From eb26d9b4e38de4dcced40cf18a051577d84bd64a Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Fri, 3 Apr 2020 14:28:33 -0700 Subject: [PATCH 001/388] working on parenting fixes --- .../qml/+android_interface/Stats.qml | 4 +++ interface/resources/qml/Stats.qml | 4 +++ interface/src/ui/Stats.cpp | 2 ++ interface/src/ui/Stats.h | 18 ++++++++++++ .../src/EntityTreeRenderer.cpp | 7 +++-- .../src/EntityTreeRenderer.h | 5 ++++ .../src/RenderableEntityItem.cpp | 20 ++++--------- .../src/RenderableEntityItem.h | 7 +---- .../src/RenderableGizmoEntityItem.cpp | 19 +++++++------ .../src/RenderableGizmoEntityItem.h | 1 + .../src/RenderableGridEntityItem.cpp | 3 +- .../src/RenderableImageEntityItem.cpp | 16 +++++------ .../RenderableParticleEffectEntityItem.cpp | 28 ++++++++++++++++--- .../src/RenderableParticleEffectEntityItem.h | 2 ++ .../src/RenderablePolyLineEntityItem.cpp | 3 +- .../src/RenderableShapeEntityItem.cpp | 7 ++--- .../src/RenderableTextEntityItem.cpp | 3 +- .../src/RenderableWebEntityItem.cpp | 3 +- .../src/RenderableZoneEntityItem.cpp | 10 ------- .../src/RenderableZoneEntityItem.h | 1 - libraries/entities/src/EntityItem.cpp | 20 ++++++++----- libraries/entities/src/EntityItem.h | 4 +-- libraries/entities/src/GizmoEntityItem.cpp | 1 + libraries/entities/src/GridEntityItem.cpp | 1 + libraries/entities/src/ImageEntityItem.cpp | 1 + libraries/entities/src/LightEntityItem.cpp | 16 +---------- libraries/entities/src/LightEntityItem.h | 3 -- libraries/entities/src/LineEntityItem.cpp | 1 + libraries/entities/src/MaterialEntityItem.cpp | 1 + libraries/entities/src/ModelEntityItem.cpp | 1 + .../entities/src/ParticleEffectEntityItem.cpp | 1 + libraries/entities/src/PolyLineEntityItem.cpp | 1 + libraries/entities/src/PolyVoxEntityItem.cpp | 1 + libraries/entities/src/ShapeEntityItem.cpp | 1 + libraries/entities/src/TextEntityItem.cpp | 1 + libraries/entities/src/WebEntityItem.cpp | 1 + libraries/entities/src/ZoneEntityItem.cpp | 1 + libraries/render-utils/src/Model.cpp | 12 ++++---- libraries/render-utils/src/Model.h | 6 ++-- 39 files changed, 134 insertions(+), 103 deletions(-) diff --git a/interface/resources/qml/+android_interface/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml index 1f07af786f..b44b95c5ca 100644 --- a/interface/resources/qml/+android_interface/Stats.qml +++ b/interface/resources/qml/+android_interface/Stats.qml @@ -386,6 +386,10 @@ Item { visible: root.expanded text: "LOD: " + root.lodStatus; } + StatText { + visible: root.expanded + text: "Entity Updates: " + root.numEntityUpdates + " / " + root.numNeededEntityUpdates; + } } } } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 23aa256cdc..9e4fdffaee 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -437,6 +437,10 @@ Item { visible: root.expanded text: "LOD: " + root.lodStatus; } + StatText { + visible: root.expanded + text: "Entity Updates: " + root.numEntityUpdates + " / " + root.numNeededEntityUpdates; + } } } } diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index 8b9b9743f0..a9e2e4d8e3 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -456,6 +456,8 @@ void Stats::updateStats(bool force) { STAT_UPDATE(localLeaves, (int)OctreeElement::getLeafNodeCount()); // LOD Details STAT_UPDATE(lodStatus, "You can see " + DependencyManager::get()->getLODFeedbackText()); + STAT_UPDATE(numEntityUpdates, DependencyManager::get()->getPrevNumEntityUpdates()); + STAT_UPDATE(numNeededEntityUpdates, DependencyManager::get()->getPrevTotalNeededEntityUpdates()); } diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index b87e3a3dbc..96f0ea725a 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -112,6 +112,8 @@ private: \ * @property {number} lodAngle - Read-only. * @property {number} lodTargetFramerate - Read-only. * @property {string} lodStatus - Read-only. + * @property {string} numEntityUpdates - Read-only. + * @property {string} numNeededEntityUpdates - Read-only. * @property {string} timingStats - Read-only. * @property {string} gameUpdateStats - Read-only. * @property {number} serverElements - Read-only. @@ -277,6 +279,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, lodAngle, 0) STATS_PROPERTY(int, lodTargetFramerate, 0) STATS_PROPERTY(QString, lodStatus, QString()) + STATS_PROPERTY(int, numEntityUpdates, 0) + STATS_PROPERTY(int, numNeededEntityUpdates, 0) STATS_PROPERTY(QString, timingStats, QString()) STATS_PROPERTY(QString, gameUpdateStats, QString()) STATS_PROPERTY(int, serverElements, 0) @@ -883,6 +887,20 @@ signals: */ void lodStatusChanged(); + /**jsdoc + * Triggered when the value of the numEntityUpdates property changes. + * @function Stats.numEntityUpdatesChanged + * @returns {Signal} + */ + void numEntityUpdatesChanged(); + + /**jsdoc + * Triggered when the value of the numNeededEntityUpdates property changes. + * @function Stats.numNeededEntityUpdatesChanged + * @returns {Signal} + */ + void numNeededEntityUpdatesChanged(); + /**jsdoc * Triggered when the value of the serverElements property changes. * @function Stats.serverElementsChanged diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index ab3f4c5243..5e05ebf5c9 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -426,6 +426,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } float expectedUpdateCost = _avgRenderableUpdateCost * _renderablesToUpdate.size(); + _prevTotalNeededEntityUpdates = _renderablesToUpdate.size(); if (expectedUpdateCost < MAX_UPDATE_RENDERABLES_TIME_BUDGET) { // we expect to update all renderables within available time budget PROFILE_RANGE_EX(simulation_physics, "UpdateRenderables", 0xffff00ff, (uint64_t)_renderablesToUpdate.size()); @@ -434,7 +435,8 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene assert(renderable); // only valid renderables are added to _renderablesToUpdate renderable->updateInScene(scene, transaction); } - size_t numRenderables = _renderablesToUpdate.size() + 1; // add one to avoid divide by zero + _prevNumEntityUpdates = _renderablesToUpdate.size(); + size_t numRenderables = _prevNumEntityUpdates + 1; // add one to avoid divide by zero _renderablesToUpdate.clear(); // compute average per-renderable update cost @@ -495,7 +497,8 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } // compute average per-renderable update cost - size_t numUpdated = sortedRenderables.size() - _renderablesToUpdate.size() + 1; // add one to avoid divide by zero + _prevNumEntityUpdates = sortedRenderables.size() - _renderablesToUpdate.size(); + size_t numUpdated = _prevNumEntityUpdates + 1; // add one to avoid divide by zero float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated); const float BLEND = 0.1f; _avgRenderableUpdateCost = (1.0f - BLEND) * _avgRenderableUpdateCost + BLEND * cost; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h index 6dbaedc123..36ec94c464 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.h +++ b/libraries/entities-renderer/src/EntityTreeRenderer.h @@ -135,6 +135,9 @@ public: static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName); static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName); + int getPrevNumEntityUpdates() const { return _prevNumEntityUpdates; } + int getPrevTotalNeededEntityUpdates() const { return _prevTotalNeededEntityUpdates; } + signals: void enterEntity(const EntityItemID& entityItemID); void leaveEntity(const EntityItemID& entityItemID); @@ -248,6 +251,8 @@ private: ReadWriteLockable _changedEntitiesGuard; std::unordered_set _changedEntities; + int _prevNumEntityUpdates { 0 }; + int _prevTotalNeededEntityUpdates { 0 }; std::unordered_set _renderablesToUpdate; std::unordered_map _entitiesInScene; diff --git a/libraries/entities-renderer/src/RenderableEntityItem.cpp b/libraries/entities-renderer/src/RenderableEntityItem.cpp index ae56a8fc47..79ee861c95 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableEntityItem.cpp @@ -201,13 +201,6 @@ void EntityRenderer::render(RenderArgs* args) { return; } - if (!_renderUpdateQueued && needsRenderUpdate()) { - // FIXME find a way to spread out the calls to needsRenderUpdate so that only a given subset of the - // items checks every frame, like 1/N of the tree ever N frames - _renderUpdateQueued = true; - emit requestRenderUpdate(); - } - if (_visible && (args->_renderMode != RenderArgs::RenderMode::DEFAULT_RENDER_MODE || !_cauterized)) { doRender(args); } @@ -326,11 +319,6 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans } _updateTime = usecTimestampNow(); - // FIXME is this excessive? - if (!needsRenderUpdate()) { - return; - } - doRenderUpdateSynchronous(scene, transaction, _entity); transaction.updateItem(_renderItemID, [this](PayloadProxyInterface& self) { if (!isValidRenderItem()) { @@ -338,7 +326,6 @@ void EntityRenderer::updateInScene(const ScenePointer& scene, Transaction& trans } // Happens on the render thread. Classes should use doRenderUpdateAsynchronous(_entity); - _renderUpdateQueued = false; }); } @@ -431,7 +418,7 @@ void EntityRenderer::doRenderUpdateSynchronous(const ScenePointer& scene, Transa } void EntityRenderer::onAddToScene(const EntityItemPointer& entity) { - QObject::connect(this, &EntityRenderer::requestRenderUpdate, this, [this] { + QObject::connect(this, &EntityRenderer::requestRenderUpdate, this, [this] { auto renderer = DependencyManager::get(); if (renderer) { renderer->onEntityChanged(_entity->getID()); @@ -440,7 +427,10 @@ void EntityRenderer::onAddToScene(const EntityItemPointer& entity) { _changeHandlerId = entity->registerChangeHandler([](const EntityItemID& changedEntity) { auto renderer = DependencyManager::get(); if (renderer) { - renderer->onEntityChanged(changedEntity); + auto renderable = renderer->renderableForEntityId(changedEntity); + if (renderable && renderable->needsRenderUpdate()) { + renderer->onEntityChanged(changedEntity); + } } }); } diff --git a/libraries/entities-renderer/src/RenderableEntityItem.h b/libraries/entities-renderer/src/RenderableEntityItem.h index 227eb6a018..d52ccf4a99 100644 --- a/libraries/entities-renderer/src/RenderableEntityItem.h +++ b/libraries/entities-renderer/src/RenderableEntityItem.h @@ -145,8 +145,6 @@ protected: PrimitiveMode _primitiveMode { PrimitiveMode::SOLID }; bool _cauterized { false }; bool _moving { false }; - // Only touched on the rendering thread - bool _renderUpdateQueued{ false }; Transform _renderTransform; std::unordered_map _materials; @@ -187,10 +185,7 @@ protected: using Parent::needsRenderUpdateFromEntity; // Returns true if the item in question needs to have updateInScene called because of changes in the entity virtual bool needsRenderUpdateFromEntity(const EntityItemPointer& entity) const override final { - if (Parent::needsRenderUpdateFromEntity(entity)) { - return true; - } - return needsRenderUpdateFromTypedEntity(_typedEntity); + return Parent::needsRenderUpdateFromEntity(entity) || needsRenderUpdateFromTypedEntity(_typedEntity); } virtual void doRenderUpdateSynchronous(const ScenePointer& scene, Transaction& transaction, const EntityItemPointer& entity) override final { diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp index 9081d0727f..7a36ae2707 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.cpp @@ -39,6 +39,16 @@ bool GizmoEntityRenderer::isTransparent() const { return Parent::isTransparent() || ringTransparent; } +void GizmoEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { + void* key = (void*)this; + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] { + withWriteLock([&] { + _renderTransform = getModelTransform(); + _renderTransform.postScale(entity->getScaledDimensions()); + }); + }); +} + void GizmoEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { bool dirty = false; RingGizmoPropertyGroup ringProperties = entity->getRingProperties(); @@ -186,15 +196,6 @@ void GizmoEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint } } } - - void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { - withWriteLock([&] { - updateModelTransformAndBound(); - _renderTransform = getModelTransform(); - _renderTransform.postScale(entity->getScaledDimensions()); - }); - }); } Item::Bound GizmoEntityRenderer::getBound() { diff --git a/libraries/entities-renderer/src/RenderableGizmoEntityItem.h b/libraries/entities-renderer/src/RenderableGizmoEntityItem.h index c072f482cd..e3d84a0201 100644 --- a/libraries/entities-renderer/src/RenderableGizmoEntityItem.h +++ b/libraries/entities-renderer/src/RenderableGizmoEntityItem.h @@ -29,6 +29,7 @@ protected: bool isTransparent() const override; private: + virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity); virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; virtual void doRender(RenderArgs* args) override; diff --git a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp index 0b074f7a81..52900d0798 100644 --- a/libraries/entities-renderer/src/RenderableGridEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableGridEntityItem.cpp @@ -41,10 +41,9 @@ void GridEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen }); void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] { withWriteLock([&] { _dimensions = entity->getScaledDimensions(); - updateModelTransformAndBound(); _renderTransform = getModelTransform(); }); }); diff --git a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp index acdf5a35de..4d19a83ae6 100644 --- a/libraries/entities-renderer/src/RenderableImageEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableImageEntityItem.cpp @@ -30,11 +30,9 @@ bool ImageEntityRenderer::isTransparent() const { } bool ImageEntityRenderer::needsRenderUpdate() const { - bool textureLoadedChanged = resultWithReadLock([&] { - return (!_textureIsLoaded && _texture && _texture->isLoaded()); - }); - - if (textureLoadedChanged) { + if (resultWithReadLock([&] { + return !_textureIsLoaded; + })) { return true; } @@ -63,15 +61,15 @@ void ImageEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce _pulseProperties = entity->getPulseProperties(); _billboardMode = entity->getBillboardMode(); - if (!_textureIsLoaded && _texture && _texture->isLoaded()) { - _textureIsLoaded = true; + if (!_textureIsLoaded) { + emit requestRenderUpdate(); } + _textureIsLoaded = _texture && (_texture->isLoaded() || _texture->isFailed()); }); void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] { withWriteLock([&] { - updateModelTransformAndBound(); _renderTransform = getModelTransform(); _renderTransform.postScale(entity->getScaledDimensions()); }); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp index f34eb85230..5ffb395302 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.cpp @@ -64,6 +64,16 @@ ParticleEffectEntityRenderer::ParticleEffectEntityRenderer(const EntityItemPoint }); } +bool ParticleEffectEntityRenderer::needsRenderUpdate() const { + if (resultWithReadLock([&] { + return !_textureLoaded; + })) { + return true; + } + + return Parent::needsRenderUpdate(); +} + void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { auto newParticleProperties = entity->getParticleProperties(); if (!newParticleProperties.valid()) { @@ -102,6 +112,7 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi } withWriteLock([&] { + _textureLoaded = true; entity->setVisuallyReady(true); }); } else { @@ -111,20 +122,29 @@ void ParticleEffectEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePoi if (textureNeedsUpdate) { withWriteLock([&] { _networkTexture = DependencyManager::get()->getTexture(_particleProperties.textures); + _textureLoaded = false; + entity->setVisuallyReady(false); }); } - if (_networkTexture) { + if (!_textureLoaded) { + emit requestRenderUpdate(); + } + + bool textureLoaded = resultWithReadLock([&] { + return _networkTexture && (_networkTexture->isLoaded() || _networkTexture->isFailed()); + }); + if (textureLoaded) { withWriteLock([&] { - entity->setVisuallyReady(_networkTexture->isFailed() || _networkTexture->isLoaded()); + entity->setVisuallyReady(true); + _textureLoaded = true; }); } } void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] { withWriteLock([&] { - updateModelTransformAndBound(); _renderTransform = getModelTransform(); }); }); diff --git a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h index cc907f2b1d..a89ab804fc 100644 --- a/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h +++ b/libraries/entities-renderer/src/RenderableParticleEffectEntityItem.h @@ -25,6 +25,7 @@ public: ParticleEffectEntityRenderer(const EntityItemPointer& entity); protected: + virtual bool needsRenderUpdate() const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; @@ -111,6 +112,7 @@ private: GeometryResource::Pointer _geometryResource; NetworkTexturePointer _networkTexture; + bool _textureLoaded { false }; ScenePointer _scene; }; diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp index e75f8593d6..a843083831 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.cpp @@ -204,9 +204,8 @@ void PolyLineEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& bool geometryChanged = uvModeStretchChanged || pointsChanged || widthsChanged || normalsChanged || colorsChanged || textureChanged || faceCameraChanged; void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, geometryChanged] () { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, geometryChanged] { withWriteLock([&] { - updateModelTransformAndBound(); _renderTransform = getModelTransform(); if (geometryChanged) { diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index d5a0e22199..222b833be2 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -70,20 +70,18 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce }); void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this] () { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] { withWriteLock([&] { - auto entity = getEntity(); _position = entity->getWorldPosition(); _dimensions = entity->getUnscaledDimensions(); // get unscaled to avoid scaling twice _orientation = entity->getWorldOrientation(); - updateModelTransformAndBound(); _renderTransform = getModelTransform(); // contains parent scale, if this entity scales with its parent if (_shape == entity::Sphere) { _renderTransform.postScale(SPHERE_ENTITY_SCALE); } _renderTransform.postScale(_dimensions); - });; + }); }); } @@ -125,6 +123,7 @@ void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPoint auto materials = _materials.find("0"); if (materials != _materials.end()) { materials->second.setNeedsUpdate(true); + emit requestRenderUpdate(); } } }); diff --git a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp index fbd10e2f5b..a5baad4841 100644 --- a/libraries/entities-renderer/src/RenderableTextEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableTextEntityItem.cpp @@ -102,10 +102,9 @@ bool TextEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint void TextEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) { void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] () { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] { withWriteLock([&] { _dimensions = entity->getScaledDimensions(); - updateModelTransformAndBound(); _renderTransform = getModelTransform(); _renderTransform.postScale(_dimensions); }); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 1da4999bad..c818eaf237 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -236,11 +236,10 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } void* key = (void*)this; - AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity]() { + AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [this, entity] { withWriteLock([&] { glm::vec2 windowSize = getWindowSize(entity); _webSurface->resize(QSize(windowSize.x, windowSize.y)); - updateModelTransformAndBound(); _renderTransform = getModelTransform(); _renderTransform.setScale(1.0f); _renderTransform.postScale(entity->getScaledDimensions()); diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp index bbb8c67ad1..d6c675e88c 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.cpp @@ -255,14 +255,6 @@ void ZoneEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scen entity->setVisuallyReady(visuallyReady); } -void ZoneEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { - if (entity->getShapeType() == SHAPE_TYPE_SPHERE) { - _renderTransform = getModelTransform(); - _renderTransform.postScale(SPHERE_ENTITY_SCALE); - } -} - - ItemKey ZoneEntityRenderer::getKey() { return ItemKey::Builder().withTypeMeta().withTagBits(getTagMask()).build(); } @@ -299,8 +291,6 @@ bool ZoneEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoint return true; } - // FIXME: do we need to trigger an update when shapeType changes? see doRenderUpdateAsynchronousTyped - return false; } diff --git a/libraries/entities-renderer/src/RenderableZoneEntityItem.h b/libraries/entities-renderer/src/RenderableZoneEntityItem.h index 5fd9b87408..d4e3d16408 100644 --- a/libraries/entities-renderer/src/RenderableZoneEntityItem.h +++ b/libraries/entities-renderer/src/RenderableZoneEntityItem.h @@ -39,7 +39,6 @@ protected: virtual void doRender(RenderArgs* args) override; virtual bool needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const override; virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override; - virtual void doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) override; private: void updateKeyZoneItemFromEntity(const TypedEntityPointer& entity); diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 856468f2ec..a48b7377df 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -615,10 +615,6 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef _lastEdited = lastEditedFromBufferAdjusted; _lastEditedFromRemote = now; _lastEditedFromRemoteInRemoteTime = lastEditedFromBuffer; - - // TODO: only send this notification if something ACTUALLY changed (hint, we haven't yet parsed - // the properties out of the bitstream (see below)) - somethingChangedNotification(); // notify derived classes that something has changed } // last updated is stored as ByteCountCoded delta from lastEdited @@ -1569,7 +1565,6 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); #endif setLastEdited(now); - somethingChangedNotification(); // notify derived classes that something has changed if (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { // anything that sets the transform or velocity must update _lastSimulated which is used // for kinematic extrapolation (e.g. we want to extrapolate forward from this moment @@ -1827,6 +1822,7 @@ void EntityItem::setPosition(const glm::vec3& value) { void EntityItem::setParentID(const QUuid& value) { QUuid oldParentID = getParentID(); if (oldParentID != value) { + _needsRenderUpdate = true; EntityTreePointer tree = getTree(); if (tree && !oldParentID.isNull()) { tree->removeFromChildrenOfAvatars(getThisPointer()); @@ -2993,10 +2989,15 @@ bool EntityItem::getCauterized() const { } void EntityItem::setCauterized(bool value) { + bool needsRenderUpdate = false; withWriteLock([&] { - _needsRenderUpdate |= _cauterized != value; + needsRenderUpdate = _cauterized != value; + _needsRenderUpdate |= needsRenderUpdate; _cauterized = value; }); + if (needsRenderUpdate) { + somethingChangedNotification(); + } } bool EntityItem::getIgnorePickIntersection() const { @@ -3031,10 +3032,15 @@ bool EntityItem::getCullWithParent() const { } void EntityItem::setCullWithParent(bool value) { + bool needsRenderUpdate = false; withWriteLock([&] { - _needsRenderUpdate |= _cullWithParent != value; + needsRenderUpdate = _cullWithParent != value; + _needsRenderUpdate |= needsRenderUpdate; _cullWithParent = value; }); + if (needsRenderUpdate) { + somethingChangedNotification(); + } } bool EntityItem::isChildOfMyAvatar() const { diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 14b8b259cc..5b0f1ad44c 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -574,8 +574,8 @@ public: bool stillHasMyGrab() const; - bool needsRenderUpdate() const { return resultWithReadLock([&] { return _needsRenderUpdate; }); } - void setNeedsRenderUpdate(bool needsRenderUpdate) { withWriteLock([&] { _needsRenderUpdate = needsRenderUpdate; }); } + bool needsRenderUpdate() const { return _needsRenderUpdate; } + void setNeedsRenderUpdate(bool needsRenderUpdate) { _needsRenderUpdate = needsRenderUpdate; } signals: void spaceUpdate(std::pair data); diff --git a/libraries/entities/src/GizmoEntityItem.cpp b/libraries/entities/src/GizmoEntityItem.cpp index a2fc691387..9d79e06534 100644 --- a/libraries/entities/src/GizmoEntityItem.cpp +++ b/libraries/entities/src/GizmoEntityItem.cpp @@ -58,6 +58,7 @@ bool GizmoEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/GridEntityItem.cpp b/libraries/entities/src/GridEntityItem.cpp index e45ab89b69..a1147b21df 100644 --- a/libraries/entities/src/GridEntityItem.cpp +++ b/libraries/entities/src/GridEntityItem.cpp @@ -70,6 +70,7 @@ bool GridEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index 2d942cc25d..c538ce619d 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -71,6 +71,7 @@ bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 2df2136639..11864f59da 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -55,21 +55,6 @@ void LightEntityItem::setUnscaledDimensions(const glm::vec3& value) { } } -void LightEntityItem::locationChanged(bool tellPhysics, bool tellChildren) { - EntityItem::locationChanged(tellPhysics, tellChildren); - withWriteLock([&] { - _needsRenderUpdate = true; - }); -} - -void LightEntityItem::dimensionsChanged() { - EntityItem::dimensionsChanged(); - withWriteLock([&] { - _needsRenderUpdate = true; - }); -} - - EntityItemProperties LightEntityItem::getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const { EntityItemProperties properties = EntityItem::getProperties(desiredProperties, allowEmptyDesiredProperties); // get the properties from our base class @@ -145,6 +130,7 @@ bool LightEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 8194ff8308..533d2994d6 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -73,9 +73,6 @@ public: static bool getLightsArePickable() { return _lightsArePickable; } static void setLightsArePickable(bool value) { _lightsArePickable = value; } - - virtual void locationChanged(bool tellPhysics, bool tellChildren) override; - virtual void dimensionsChanged() override; virtual bool supportsDetailedIntersection() const override { return true; } virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction, diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 1cd09c1a0c..45cdf980a1 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -61,6 +61,7 @@ bool LineEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 73bebfc403..11745c29ce 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -60,6 +60,7 @@ bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 0b6e62e89e..aa0c7304a7 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -111,6 +111,7 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); + somethingChangedNotification(); } return somethingChanged; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index c20ae87276..082c4efb17 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -759,6 +759,7 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index fe14ba6925..fcd4b6d89d 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -77,6 +77,7 @@ bool PolyLineEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index b424ba137f..c31d0b81b3 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -130,6 +130,7 @@ bool PolyVoxEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 882ab26901..5b9c3e6881 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -186,6 +186,7 @@ bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties.getLastEdited()); + somethingChangedNotification(); } return somethingChanged; } diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index bcc2c0efed..9c53196247 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -109,6 +109,7 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); + somethingChangedNotification(); } return somethingChanged; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index a62f599e4c..d8b2c8fc3d 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -92,6 +92,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); + somethingChangedNotification(); } return somethingChanged; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 0771d9ad54..dc18c05ef3 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -88,6 +88,7 @@ bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { "now=" << now << " getLastEdited()=" << getLastEdited(); } setLastEdited(properties._lastEdited); + somethingChangedNotification(); } return somethingChanged; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index d8172112ff..d3f2a04d24 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -282,11 +282,6 @@ void Model::setRenderItemsNeedUpdate() { emit requestRenderUpdate(); } -void Model::setPrimitiveMode(PrimitiveMode primitiveMode) { - _primitiveMode = primitiveMode; - setRenderItemsNeedUpdate(); -} - void Model::reset() { if (isLoaded()) { const HFMModel& hfmModel = getHFMModel(); @@ -888,6 +883,13 @@ void Model::updateRenderItemsKey(const render::ScenePointer& scene) { scene->enqueueTransaction(transaction); } +void Model::setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene) { + if (_primitiveMode != primitiveMode) { + _primitiveMode = primitiveMode; + updateRenderItemsKey(scene); + } +} + void Model::setVisibleInScene(bool visible, const render::ScenePointer& scene) { if (Model::isVisible() != visible) { auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index b677861e9f..85aaa33bcb 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -129,6 +129,9 @@ public: bool isCauterized() const { return _cauterized; } void setCauterized(bool value, const render::ScenePointer& scene); + void setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene = nullptr); + PrimitiveMode getPrimitiveMode() const { return _primitiveMode; } + void setCullWithParent(bool value); // Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model. @@ -168,9 +171,6 @@ public: bool isLoaded() const { return (bool)_renderGeometry && _renderGeometry->isHFMModelLoaded(); } bool isAddedToScene() const { return _addedToScene; } - void setPrimitiveMode(PrimitiveMode primitiveMode); - PrimitiveMode getPrimitiveMode() const { return _primitiveMode; } - void reset(); void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint); From a8c8c775f3a6415bd4876e1ed5512664757b6186 Mon Sep 17 00:00:00 2001 From: Marcus Llewellyn Date: Sat, 18 Apr 2020 12:32:08 -0500 Subject: [PATCH 002/388] Re-enable and update code signing in installer template --- cmake/templates/NSIS.template.in | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cmake/templates/NSIS.template.in b/cmake/templates/NSIS.template.in index 0e4c2f3579..ded90494bf 100644 --- a/cmake/templates/NSIS.template.in +++ b/cmake/templates/NSIS.template.in @@ -204,13 +204,13 @@ ; The Inner invocation has written an uninstaller binary for us. ; We need to sign it if it's a production or PR build. - ; !if @PRODUCTION_BUILD@ == 1 - ; !if @BYPASS_SIGNING@ == 1 - ; !warning "BYPASS_SIGNING set - installer will not be signed" - ; !else - ; !system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://sha256timestamp.ws.symantec.com/sha256/timestamp /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 - ; !endif - ; !endif + !if @PRODUCTION_BUILD@ == 1 + !if @BYPASS_SIGNING@ == 1 + !warning "BYPASS_SIGNING set - installer will not be signed" + !else + !system '"@SIGNTOOL_EXECUTABLE@" sign /fd sha256 /f %HF_PFX_FILE% /p %HF_PFX_PASSPHRASE% /tr http://timestamp.comodoca.com?td=sha256 /td SHA256 $%TEMP%\@UNINSTALLER_NAME@' = 0 + !endif + !endif ; Good. Now we can carry on writing the real installer. From b6cb1fcfe723a5f84ca4854665c1d2eeb8231f6a Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Wed, 22 Apr 2020 16:36:51 -0700 Subject: [PATCH 003/388] fix procedural applying to other things --- .../src/RenderableModelEntityItem.cpp | 29 +++++++++++++++++++ .../src/RenderableModelEntityItem.h | 1 + .../src/RenderableShapeEntityItem.cpp | 6 ++-- .../render-utils/src/MeshPartPayload.cpp | 3 +- 4 files changed, 34 insertions(+), 5 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 5fbbdfa0b8..c70220506e 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -1442,6 +1442,31 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce emit requestRenderUpdate(); } + if (!_allProceduralMaterialsLoaded) { + std::lock_guard lock(_materialsLock); + bool allProceduralMaterialsLoaded = true; + for (auto& shapeMaterialPair : _materials) { + auto material = shapeMaterialPair.second; + while (!material.empty()) { + auto mat = material.top(); + if (mat.material && mat.material->isProcedural() && !mat.material->isReady()) { + allProceduralMaterialsLoaded = false; + break; + } + material.pop(); + } + if (!allProceduralMaterialsLoaded) { + break; + } + } + if (!allProceduralMaterialsLoaded) { + emit requestRenderUpdate(); + } else { + _allProceduralMaterialsLoaded = true; + model->setRenderItemsNeedUpdate(); + } + } + // When the individual mesh parts of a model finish fading, they will mark their Model as needing updating // we will watch for that and ask the model to update it's render items if (model->getRenderItemsNeedUpdate()) { @@ -1559,6 +1584,10 @@ void ModelEntityRenderer::addMaterial(graphics::MaterialLayer material, const st if (_model && _model->fetchRenderItemIDs().size() > 0) { _model->addMaterial(material, parentMaterialName); } + if (material.material && material.material->isProcedural()) { + _allProceduralMaterialsLoaded = false; + emit requestRenderUpdate(); + } } void ModelEntityRenderer::removeMaterial(graphics::MaterialPointer material, const std::string& parentMaterialName) { diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h index 0119c7bc26..7c219422a6 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.h +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h @@ -198,6 +198,7 @@ private: bool _prevModelLoaded { false }; void processMaterials(); + bool _allProceduralMaterialsLoaded { false }; }; } } // namespace diff --git a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp index 222b833be2..38dd3b2160 100644 --- a/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableShapeEntityItem.cpp @@ -32,7 +32,7 @@ bool ShapeEntityRenderer::needsRenderUpdate() const { if (resultWithReadLock([&] { auto mat = _materials.find("0"); if (mat != _materials.end() && mat->second.top().material && mat->second.top().material->isProcedural() && - mat->second.top().material->isEnabled()) { + mat->second.top().material->isReady()) { auto procedural = std::static_pointer_cast(mat->second.top().material); if (procedural->isFading()) { return true; @@ -88,7 +88,7 @@ void ShapeEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce void ShapeEntityRenderer::doRenderUpdateAsynchronousTyped(const TypedEntityPointer& entity) { withReadLock([&] { auto mat = _materials.find("0"); - if (mat != _materials.end() && mat->second.top().material && mat->second.top().material->isProcedural() && mat->second.top().material->isEnabled()) { + if (mat != _materials.end() && mat->second.top().material && mat->second.top().material->isProcedural() && mat->second.top().material->isReady()) { auto procedural = std::static_pointer_cast(mat->second.top().material); if (procedural->isFading()) { procedural->setIsFading(Interpolate::calculateFadeRatio(procedural->getFadeStartTime()) < 1.0f); @@ -136,7 +136,7 @@ bool ShapeEntityRenderer::isTransparent() const { auto mat = _materials.find("0"); if (mat != _materials.end() && mat->second.top().material) { - if (mat->second.top().material->isProcedural() && mat->second.top().material->isEnabled()) { + if (mat->second.top().material->isProcedural() && mat->second.top().material->isReady()) { auto procedural = std::static_pointer_cast(mat->second.top().material); if (procedural->isFading()) { return true; diff --git a/libraries/render-utils/src/MeshPartPayload.cpp b/libraries/render-utils/src/MeshPartPayload.cpp index e82af5395f..544947ab59 100644 --- a/libraries/render-utils/src/MeshPartPayload.cpp +++ b/libraries/render-utils/src/MeshPartPayload.cpp @@ -493,8 +493,7 @@ void ModelMeshPartPayload::render(RenderArgs* args) { batch.setDrawcallUniform(drawcallInfo); } - if (!_drawMaterials.empty() && _drawMaterials.top().material && _drawMaterials.top().material->isProcedural() && - _drawMaterials.top().material->isReady()) { + if (_shapeKey.hasOwnPipeline()) { if (!(enableMaterialProceduralShaders)) { return; } From 7c264e7d852e58d52e389bf15a630c5f2757c956 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Mon, 15 Jun 2020 22:51:51 -0400 Subject: [PATCH 004/388] First pass at adding domain metadata exporter. --- .../resources/describe-settings.json | 26 +++++++++++++++- .../resources/metadata_exporter/index.html | 14 +++++++++ domain-server/src/DomainMetadata.cpp | 19 ++++++++++++ domain-server/src/DomainMetadata.h | 4 +++ domain-server/src/DomainServer.cpp | 31 +++++++++++++++++-- domain-server/src/DomainServer.h | 4 ++- libraries/networking/src/DomainHandler.h | 9 +++++- 7 files changed, 101 insertions(+), 6 deletions(-) create mode 100644 domain-server/resources/metadata_exporter/index.html diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 284dd344e7..6dd8e64981 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -54,7 +54,23 @@ "default": true, "type": "checkbox", "advanced": true - } + }, + { + "name": "enable_metadata_exporter", + "label": "Enable Metadata HTTP Availability", + "help": "Allows your domain's metadata to be accessible on the public internet via direct HTTP connection to the domain server.", + "default": true, + "type": "checkbox", + "advanced": true + }, + { + "name": "metadata_exporter_port", + "label": "Metadata Exporter HTTP Port", + "help": "This is the port where the Metaverse exporter accepts connections. It listens both on IPv4 and IPv6 and can be accessed remotely, so you should make sure to restrict access with a firewall as needed.", + "default": "9704", + "type": "int", + "advanced": true + }, ] }, { @@ -210,6 +226,14 @@ "help": "Must match the password entered above for change to be saved.", "value-hidden": true }, + { + "name": "approved_safe_urls", + "label": "Approved Script and QML URLs", + "help": "These URLs will be sent to the Interface as safe URLs to allow through the whitelist if the Interface has this security option enabled.", + "placeholder": "0", + "default": "1", + "advanced": false + }, { "name": "maximum_user_capacity", "label": "Maximum User Capacity", diff --git a/domain-server/resources/metadata_exporter/index.html b/domain-server/resources/metadata_exporter/index.html new file mode 100644 index 0000000000..acc7aae23a --- /dev/null +++ b/domain-server/resources/metadata_exporter/index.html @@ -0,0 +1,14 @@ + + + Vircadia Metadata exporter + + + +

Vircadia Metadata exporter

+ +

If you can see this page, this means that your domain's metadata is available to be exported.

+

+ Metadata +

+ + diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index 2540858742..26dbc16184 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -4,6 +4,7 @@ // // Created by Zach Pomerantz on 5/25/2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -14,10 +15,13 @@ #include #include #include +#include #include "DomainServer.h" #include "DomainServerNodeData.h" +Q_LOGGING_CATEGORY(domain_metadata_exporter, "hifi.domain_server.metadata_exporter") + const QString DomainMetadata::USERS = "users"; const QString DomainMetadata::Users::NUM_TOTAL = "num_users"; const QString DomainMetadata::Users::NUM_ANON = "num_anon_users"; @@ -215,3 +219,18 @@ void DomainMetadata::sendDescriptors() { #endif } } + +bool DomainMetadata::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { + const QString URI_METADATA = "/metadata"; + const QString EXPORTER_MIME_TYPE = "text/plain"; + + qCDebug(domain_metadata_exporter) << "Request on URL " << url; + + if (url.path() == URI_METADATA) { + QString domainMetadataJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); + connection->respond(HTTPConnection::StatusCode200, domainMetadataJSON.toUtf8(), qPrintable(EXPORTER_MIME_TYPE)); + return true; + } + + return false; +} diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h index ed4e324464..85422692b9 100644 --- a/domain-server/src/DomainMetadata.h +++ b/domain-server/src/DomainMetadata.h @@ -15,6 +15,7 @@ #include #include +#include "HTTPManager.h" class DomainMetadata : public QObject { Q_OBJECT @@ -43,6 +44,9 @@ public: DomainMetadata(QObject* domainServer); DomainMetadata() = delete; + + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; + bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false) override; // Get cached metadata QJsonObject get(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 49b319e31d..1f7df37fc6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -70,7 +70,6 @@ const QString DomainServer::REPLACEMENT_FILE_EXTENSION = ".replace"; const int MIN_PORT = 1; const int MAX_PORT = 65535; - int const DomainServer::EXIT_CODE_REBOOT = 234923; #if USE_STABLE_GLOBAL_SERVICES @@ -334,6 +333,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : _nodePingMonitorTimer->start(NODE_PING_MONITOR_INTERVAL_MSECS); initializeExporter(); + initializeMetadataExporter(); } void DomainServer::parseCommandLine(int argc, char* argv[]) { @@ -427,6 +427,11 @@ DomainServer::~DomainServer() { _contentManager->aboutToFinish(); _contentManager->terminate(); } + + if (_httpMetadataExporterManager) { + _httpMetadataExporterManager->close(); + delete _httpMetadataExporterManager; + } if (_httpExporterManager) { _httpExporterManager->close(); @@ -3045,8 +3050,7 @@ void DomainServer::updateUpstreamNodes() { updateReplicationNodes(Upstream); } -void DomainServer::initializeExporter() -{ +void DomainServer::initializeExporter() { static const QString ENABLE_EXPORTER = "monitoring.enable_prometheus_exporter"; static const QString EXPORTER_PORT = "monitoring.prometheus_exporter_port"; @@ -3066,6 +3070,27 @@ void DomainServer::initializeExporter() } } +void DomainServer::initializeMetadataExporter() +{ + static const QString ENABLE_EXPORTER = "metaverse.enable_metadata_exporter"; + static const QString EXPORTER_PORT = "metaverse.metadata_exporter_port"; + + bool isMetadataExporterEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_EXPORTER).toBool(); + int metadataExporterPort = _settingsManager.valueOrDefaultValueForKeyPath(EXPORTER_PORT).toInt(); + + if (exporterPort < MIN_PORT || exporterPort > MAX_PORT) { + qCWarning(domain_server) << "Metadata exporter port " << metadataExporterPort << " is out of range."; + isMetadataExporterEnabled = false; + } + + qCDebug(domain_server) << "Setting up Metadata exporter."; + + if (isMetadataExporterEnabled && !_httpMetadataExporterManager) { + qCInfo(domain_server) << "Starting Metadata exporter on port " << metadataExporterPort; + _httpMetadataExporterManager = new HTTPManager(QHostAddress::Any, (quint16)metadataExporterPort, QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), &_metadata); + } +} + void DomainServer::updateReplicatedNodes() { // Make sure we have downstream nodes in our list static const QString REPLICATED_USERS_KEY = "users"; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 7c0fa5fb15..0334404863 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -73,7 +73,6 @@ public: static int const EXIT_CODE_REBOOT; bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; - bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false) override; static const QString REPLACEMENT_FILE_EXTENSION; @@ -140,6 +139,7 @@ private slots: void updateDownstreamNodes(); void updateUpstreamNodes(); void initializeExporter(); + void initializeMetadataExporter(); void tokenGrantFinished(); void profileRequestFinished(); @@ -240,6 +240,8 @@ private: HTTPManager _httpManager; HTTPManager* _httpExporterManager { nullptr }; + HTTPManager* _httpMetadataExporterManager; + std::unique_ptr _httpsManager; QHash _allAssignments; diff --git a/libraries/networking/src/DomainHandler.h b/libraries/networking/src/DomainHandler.h index 178c56c34a..50ebc1edbc 100644 --- a/libraries/networking/src/DomainHandler.h +++ b/libraries/networking/src/DomainHandler.h @@ -72,7 +72,14 @@ const quint16 DOMAIN_SERVER_EXPORTER_PORT = .value("VIRCADIA_DOMAIN_SERVER_EXPORTER_PORT") .toUInt() : 9703; - + +const quint16 DOMAIN_SERVER_METADATA_EXPORTER_PORT = + QProcessEnvironment::systemEnvironment() + .contains("DOMAIN_SERVER_METADATA_EXPORTER_PORT") + ? QProcessEnvironment::systemEnvironment() + .value("DOMAIN_SERVER_METADATA_EXPORTER_PORT") + .toUInt() + : 9704; const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; From 269f0ac6002cd0933d9b9973285b3449982fcb31 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Mon, 15 Jun 2020 23:49:54 -0400 Subject: [PATCH 005/388] Latest condition. --- domain-server/src/DomainMetadata.cpp | 5 +++-- domain-server/src/DomainMetadata.h | 7 ++++--- domain-server/src/DomainServer.cpp | 7 +++---- domain-server/src/DomainServer.h | 2 ++ 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index 26dbc16184..f9f79d0846 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -10,6 +10,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html #include "DomainMetadata.h" +#include "HTTPConnection.h" #include #include @@ -220,14 +221,14 @@ void DomainMetadata::sendDescriptors() { } } -bool DomainMetadata::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { +bool DomainMetadataExporter::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { + QString domainMetadataJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); const QString URI_METADATA = "/metadata"; const QString EXPORTER_MIME_TYPE = "text/plain"; qCDebug(domain_metadata_exporter) << "Request on URL " << url; if (url.path() == URI_METADATA) { - QString domainMetadataJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); connection->respond(HTTPConnection::StatusCode200, domainMetadataJSON.toUtf8(), qPrintable(EXPORTER_MIME_TYPE)); return true; } diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h index 85422692b9..eade025a0a 100644 --- a/domain-server/src/DomainMetadata.h +++ b/domain-server/src/DomainMetadata.h @@ -17,6 +17,10 @@ #include #include "HTTPManager.h" +class DomainMetadataExporter : public HTTPRequestHandler { + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); +}; + class DomainMetadata : public QObject { Q_OBJECT @@ -45,9 +49,6 @@ public: DomainMetadata(QObject* domainServer); DomainMetadata() = delete; - bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; - bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false) override; - // Get cached metadata QJsonObject get(); QJsonObject get(const QString& group); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 1f7df37fc6..d244fc99c6 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3070,15 +3070,14 @@ void DomainServer::initializeExporter() { } } -void DomainServer::initializeMetadataExporter() -{ +void DomainServer::initializeMetadataExporter() { static const QString ENABLE_EXPORTER = "metaverse.enable_metadata_exporter"; static const QString EXPORTER_PORT = "metaverse.metadata_exporter_port"; bool isMetadataExporterEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_EXPORTER).toBool(); int metadataExporterPort = _settingsManager.valueOrDefaultValueForKeyPath(EXPORTER_PORT).toInt(); - if (exporterPort < MIN_PORT || exporterPort > MAX_PORT) { + if (metadataExporterPort < MIN_PORT || metadataExporterPort > MAX_PORT) { qCWarning(domain_server) << "Metadata exporter port " << metadataExporterPort << " is out of range."; isMetadataExporterEnabled = false; } @@ -3087,7 +3086,7 @@ void DomainServer::initializeMetadataExporter() if (isMetadataExporterEnabled && !_httpMetadataExporterManager) { qCInfo(domain_server) << "Starting Metadata exporter on port " << metadataExporterPort; - _httpMetadataExporterManager = new HTTPManager(QHostAddress::Any, (quint16)metadataExporterPort, QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), &_metadata); + _httpMetadataExporterManager = new HTTPManager(QHostAddress::Any, (quint16)metadataExporterPort, QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), &_metadataExporter); } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 0334404863..34555861af 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -73,6 +73,7 @@ public: static int const EXIT_CODE_REBOOT; bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; + bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false) override; static const QString REPLACEMENT_FILE_EXTENSION; @@ -237,6 +238,7 @@ private: DomainGatekeeper _gatekeeper; DomainServerExporter _exporter; + DomainMetadata _metadataExporter; HTTPManager _httpManager; HTTPManager* _httpExporterManager { nullptr }; From 19ff5bfd4f6ffa817bf64e0f477b247f50a630d7 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 16 Jun 2020 00:53:06 -0400 Subject: [PATCH 006/388] uno error. --- domain-server/src/DomainMetadata.cpp | 2 +- domain-server/src/DomainMetadata.h | 8 +++----- domain-server/src/DomainServer.h | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index f9f79d0846..b920ab022b 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -221,7 +221,7 @@ void DomainMetadata::sendDescriptors() { } } -bool DomainMetadataExporter::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { +bool DomainMetadata::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { QString domainMetadataJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); const QString URI_METADATA = "/metadata"; const QString EXPORTER_MIME_TYPE = "text/plain"; diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h index eade025a0a..4c4f8ecf3c 100644 --- a/domain-server/src/DomainMetadata.h +++ b/domain-server/src/DomainMetadata.h @@ -17,11 +17,7 @@ #include #include "HTTPManager.h" -class DomainMetadataExporter : public HTTPRequestHandler { - bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); -}; - -class DomainMetadata : public QObject { +class DomainMetadata : public QObject, public HTTPRequestHandler { Q_OBJECT public: @@ -53,6 +49,8 @@ public: QJsonObject get(); QJsonObject get(const QString& group); + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); + public slots: void descriptorsChanged(); void securityChanged(bool send); diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 34555861af..a4a47f5f46 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -242,7 +242,7 @@ private: HTTPManager _httpManager; HTTPManager* _httpExporterManager { nullptr }; - HTTPManager* _httpMetadataExporterManager; + HTTPManager* _httpMetadataExporterManager { nullptr }; std::unique_ptr _httpsManager; From 8052259489d3a3df6af899f4397d058f70ea2b50 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Wed, 17 Jun 2020 17:47:50 -0700 Subject: [PATCH 007/388] get domainmetadata stuff compiling --- domain-server/src/DomainMetadata.cpp | 13 +------------ domain-server/src/DomainMetadata.h | 5 ++--- domain-server/src/DomainServer.cpp | 11 +++++++++-- domain-server/src/DomainServer.h | 1 - 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index b920ab022b..262d4ea0f1 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -54,22 +54,11 @@ const QString DomainMetadata::Descriptors::TAGS = "tags"; // // it is meant to be sent to and consumed by an external API -DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) { +DomainMetadata::DomainMetadata() { // set up the structure necessary for casting during parsing _metadata[USERS] = QVariantMap {}; _metadata[DESCRIPTORS] = QVariantMap {}; - assert(dynamic_cast(domainServer)); - DomainServer* server = static_cast(domainServer); - - // update the metadata when a user (dis)connects - connect(server, &DomainServer::userConnected, this, &DomainMetadata::usersChanged); - connect(server, &DomainServer::userDisconnected, this, &DomainMetadata::usersChanged); - - // update the metadata when security changes - connect(&server->_settingsManager, &DomainServerSettingsManager::updateNodePermissions, - this, static_cast(&DomainMetadata::securityChanged)); - // initialize the descriptors securityChanged(false); descriptorsChanged(); diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h index 4c4f8ecf3c..ba6b1b88a6 100644 --- a/domain-server/src/DomainMetadata.h +++ b/domain-server/src/DomainMetadata.h @@ -42,8 +42,8 @@ public: static const QString TAGS; }; - DomainMetadata(QObject* domainServer); - DomainMetadata() = delete; + DomainMetadata(); + ~DomainMetadata() = default; // Get cached metadata QJsonObject get(); @@ -54,7 +54,6 @@ public: public slots: void descriptorsChanged(); void securityChanged(bool send); - void securityChanged() { securityChanged(true); } void usersChanged(); protected: diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index d244fc99c6..7ff0459f21 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -268,10 +268,17 @@ DomainServer::DomainServer(int argc, char* argv[]) : } // send signal to DomainMetadata when descriptors changed - _metadata = new DomainMetadata(this); + _metadata = new DomainMetadata(); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, _metadata, &DomainMetadata::descriptorsChanged); + // update the metadata when a user (dis)connects + connect(this, &DomainServer::userConnected, _metadata, &DomainMetadata::usersChanged); + connect(this, &DomainServer::userDisconnected, _metadata, &DomainMetadata::usersChanged); + + // update the metadata when security changes + connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions, [this] { _metadata->securityChanged(true); }); + qDebug() << "domain-server is running"; static const QString AC_SUBNET_WHITELIST_SETTING_PATH = "security.ac_subnet_whitelist"; @@ -3086,7 +3093,7 @@ void DomainServer::initializeMetadataExporter() { if (isMetadataExporterEnabled && !_httpMetadataExporterManager) { qCInfo(domain_server) << "Starting Metadata exporter on port " << metadataExporterPort; - _httpMetadataExporterManager = new HTTPManager(QHostAddress::Any, (quint16)metadataExporterPort, QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), &_metadataExporter); + _httpMetadataExporterManager = new HTTPManager(QHostAddress::Any, (quint16)metadataExporterPort, QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), _metadata); } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index a4a47f5f46..c27500d5ce 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -238,7 +238,6 @@ private: DomainGatekeeper _gatekeeper; DomainServerExporter _exporter; - DomainMetadata _metadataExporter; HTTPManager _httpManager; HTTPManager* _httpExporterManager { nullptr }; From 1da1dd20c383cbf28a53360b1a6301403e3285a4 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Wed, 17 Jun 2020 20:18:05 -0700 Subject: [PATCH 008/388] try to fix crash --- domain-server/src/DomainMetadata.cpp | 2 +- domain-server/src/DomainMetadata.h | 2 +- domain-server/src/DomainServer.cpp | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index 262d4ea0f1..755f3ec148 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -54,7 +54,7 @@ const QString DomainMetadata::Descriptors::TAGS = "tags"; // // it is meant to be sent to and consumed by an external API -DomainMetadata::DomainMetadata() { +DomainMetadata::DomainMetadata(QObject* domainServer) : QObject(domainServer) { // set up the structure necessary for casting during parsing _metadata[USERS] = QVariantMap {}; _metadata[DESCRIPTORS] = QVariantMap {}; diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h index ba6b1b88a6..6fae29248d 100644 --- a/domain-server/src/DomainMetadata.h +++ b/domain-server/src/DomainMetadata.h @@ -42,7 +42,7 @@ public: static const QString TAGS; }; - DomainMetadata(); + DomainMetadata(QObject* domainServer); ~DomainMetadata() = default; // Get cached metadata diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 7ff0459f21..3f70d95421 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -268,7 +268,7 @@ DomainServer::DomainServer(int argc, char* argv[]) : } // send signal to DomainMetadata when descriptors changed - _metadata = new DomainMetadata(); + _metadata = new DomainMetadata(this); connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, _metadata, &DomainMetadata::descriptorsChanged); From f712fc7735b7861851e0180463fd18203d669661 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Thu, 18 Jun 2020 16:43:59 -0400 Subject: [PATCH 009/388] Export to JSON, JSON formatting. --- domain-server/resources/describe-settings.json | 16 ++++++++-------- domain-server/src/DomainMetadata.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 6dd8e64981..be40611f14 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -54,7 +54,7 @@ "default": true, "type": "checkbox", "advanced": true - }, + }, { "name": "enable_metadata_exporter", "label": "Enable Metadata HTTP Availability", @@ -64,13 +64,13 @@ "advanced": true }, { - "name": "metadata_exporter_port", - "label": "Metadata Exporter HTTP Port", - "help": "This is the port where the Metaverse exporter accepts connections. It listens both on IPv4 and IPv6 and can be accessed remotely, so you should make sure to restrict access with a firewall as needed.", - "default": "9704", - "type": "int", - "advanced": true - }, + "name": "metadata_exporter_port", + "label": "Metadata Exporter HTTP Port", + "help": "This is the port where the Metaverse exporter accepts connections. It listens both on IPv4 and IPv6 and can be accessed remotely, so you should make sure to restrict access with a firewall as needed.", + "default": "9704", + "type": "int", + "advanced": true + } ] }, { diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index 755f3ec148..e30cefa1db 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -213,7 +213,7 @@ void DomainMetadata::sendDescriptors() { bool DomainMetadata::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { QString domainMetadataJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); const QString URI_METADATA = "/metadata"; - const QString EXPORTER_MIME_TYPE = "text/plain"; + const QString EXPORTER_MIME_TYPE = "application/json"; qCDebug(domain_metadata_exporter) << "Request on URL " << url; From 7038cfdeb327d9f4ff74b350740a1184befa1312 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Wed, 10 Jun 2020 20:28:45 -0700 Subject: [PATCH 010/388] handle C++ exceptions, unpacking the internal structures to get the variable type (and any superclasses) being thrown --- interface/src/CrashHandler_Crashpad.cpp | 103 +++++++++++++++++++++++- 1 file changed, 100 insertions(+), 3 deletions(-) diff --git a/interface/src/CrashHandler_Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp index 900a296955..6ce8d170e2 100644 --- a/interface/src/CrashHandler_Crashpad.cpp +++ b/interface/src/CrashHandler_Crashpad.cpp @@ -16,11 +16,13 @@ #include #include +#include #include -#include -#include -#include +#include +#include +#include +#include #if defined(__clang__) @@ -59,6 +61,10 @@ static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler" }; #ifdef Q_OS_WIN #include +#include + +void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); + LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { if (!client) { @@ -70,8 +76,99 @@ LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { client->DumpAndCrash(pExceptionInfo); } + if (pExceptionInfo->ExceptionRecord->ExceptionCode == 0xE06D7363) { + fatalCxxException(pExceptionInfo); + client->DumpAndCrash(pExceptionInfo); + } + return EXCEPTION_CONTINUE_SEARCH; } + +#pragma pack(push, ehdata, 4) + +struct PMD_internal { + int mdisp; + int pdisp; + int vdisp; +}; + +struct ThrowInfo_internal { + __int32 attributes; + __int32 pmfnUnwind; // 32-bit RVA + __int32 pForwardCompat; // 32-bit RVA + __int32 pCatchableTypeArray; // 32-bit RVA +}; + +struct CatchableType_internal { + __int32 properties; + __int32 pType; // 32-bit RVA + PMD_internal thisDisplacement; + __int32 sizeOrOffset; + __int32 copyFunction; // 32-bit RVA +}; + +#pragma warning(disable : 4200) +struct CatchableTypeArray_internal { + int nCatchableTypes; + __int32 arrayOfCatchableTypes[0]; // 32-bit RVA +}; +#pragma warning(default : 4200) + +#pragma pack(pop, ehdata) + +// everything inside this function is extremely undocumented, attempting to extract +// the underlying C++ exception type (or at least its name) before throwing the whole +// mess at crashpad +void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { + PEXCEPTION_RECORD ExceptionRecord = pExceptionInfo->ExceptionRecord; + + if(ExceptionRecord->NumberParameters != 4 || ExceptionRecord->ExceptionInformation[0] != 0x19930520) { + // doesn't match expected parameter counts or magic numbers + return; + } + + //ULONG_PTR signature = ExceptionRecord->ExceptionInformation[0]; + void* pExceptionObject = reinterpret_cast(ExceptionRecord->ExceptionInformation[1]); // the object that generated the exception + ThrowInfo_internal* pThrowInfo = reinterpret_cast(ExceptionRecord->ExceptionInformation[2]); + ULONG_PTR moduleBase = ExceptionRecord->ExceptionInformation[3]; + if(moduleBase == 0 || pThrowInfo == NULL) { + return; // broken assumption + } + + // now we start breaking the pThrowInfo internal structure apart + if(pThrowInfo->pCatchableTypeArray == 0) { + return; // broken assumption + } + CatchableTypeArray_internal* pCatchableTypeArray = reinterpret_cast(moduleBase + pThrowInfo->pCatchableTypeArray); + if(pCatchableTypeArray->nCatchableTypes == 0 || pCatchableTypeArray->arrayOfCatchableTypes[0] == 0) { + return; // broken assumption + } + CatchableType_internal* pCatchableType = reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[0]); + if(pCatchableType->pType == 0) { + return; // broken assumption + } + const std::type_info* type = reinterpret_cast(moduleBase + pCatchableType->pType); + + // we're crashing, not really sure it matters who's currently holding the lock (although this could in theory really mess things up + annotationMutex.try_lock(); + crashpadAnnotations->SetKeyValue("thrownObject", type->name()); + + QString compatibleObjects; + for (int catchTypeIdx = 1; catchTypeIdx < pCatchableTypeArray->nCatchableTypes; catchTypeIdx++) { + CatchableType_internal* pCatchableSuperclassType = reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[catchTypeIdx]); + if (pCatchableSuperclassType->pType == 0) { + return; // broken assumption + } + const std::type_info* superclassType = reinterpret_cast(moduleBase + pCatchableSuperclassType->pType); + + if (!compatibleObjects.isEmpty()) { + compatibleObjects += ", "; + } + compatibleObjects += superclassType->name(); + } + crashpadAnnotations->SetKeyValue("thrownObjectLike", compatibleObjects.toStdString()); +} + #endif bool startCrashHandler(std::string appPath) { From 9af8b6b452e3e03f5e9801bcd0a5ff1dae68a122 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Sat, 13 Jun 2020 16:32:17 -0700 Subject: [PATCH 011/388] documenting the magic numbers a bit better (or just giving them names) --- interface/src/CrashHandler_Crashpad.cpp | 23 ++++++++++++++++++----- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/interface/src/CrashHandler_Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp index 6ce8d170e2..2ddcd3f398 100644 --- a/interface/src/CrashHandler_Crashpad.cpp +++ b/interface/src/CrashHandler_Crashpad.cpp @@ -49,6 +49,9 @@ using namespace crashpad; static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; +static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; +static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; + CrashpadClient* client { nullptr }; std::mutex annotationMutex; crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; @@ -76,7 +79,7 @@ LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { client->DumpAndCrash(pExceptionInfo); } - if (pExceptionInfo->ExceptionRecord->ExceptionCode == 0xE06D7363) { + if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_MSVC_CPP_EXCEPTION) { fatalCxxException(pExceptionInfo); client->DumpAndCrash(pExceptionInfo); } @@ -121,21 +124,27 @@ struct CatchableTypeArray_internal { // mess at crashpad void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { PEXCEPTION_RECORD ExceptionRecord = pExceptionInfo->ExceptionRecord; + /* + Exception arguments for Microsoft C++ exceptions: + [0] signature - magic number + [1] void* - variable that is being thrown + [2] ThrowInfo* - description of the variable that was thrown + [3] HMODULE - (64-bit only) base address that all 32bit pointers are added to + */ - if(ExceptionRecord->NumberParameters != 4 || ExceptionRecord->ExceptionInformation[0] != 0x19930520) { + if (ExceptionRecord->NumberParameters != 4 || ExceptionRecord->ExceptionInformation[0] != MSVC_CPP_EXCEPTION_SIGNATURE) { // doesn't match expected parameter counts or magic numbers return; } - //ULONG_PTR signature = ExceptionRecord->ExceptionInformation[0]; - void* pExceptionObject = reinterpret_cast(ExceptionRecord->ExceptionInformation[1]); // the object that generated the exception + // get the ThrowInfo struct from the exception arguments ThrowInfo_internal* pThrowInfo = reinterpret_cast(ExceptionRecord->ExceptionInformation[2]); ULONG_PTR moduleBase = ExceptionRecord->ExceptionInformation[3]; if(moduleBase == 0 || pThrowInfo == NULL) { return; // broken assumption } - // now we start breaking the pThrowInfo internal structure apart + // get the CatchableTypeArray* struct from ThrowInfo if(pThrowInfo->pCatchableTypeArray == 0) { return; // broken assumption } @@ -143,6 +152,8 @@ void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { if(pCatchableTypeArray->nCatchableTypes == 0 || pCatchableTypeArray->arrayOfCatchableTypes[0] == 0) { return; // broken assumption } + + // get the CatchableType struct for the actual exception type from CatchableTypeArray CatchableType_internal* pCatchableType = reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[0]); if(pCatchableType->pType == 0) { return; // broken assumption @@ -153,6 +164,8 @@ void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { annotationMutex.try_lock(); crashpadAnnotations->SetKeyValue("thrownObject", type->name()); + // After annotating the name of the actual object type, go through the other entries in CatcahleTypeArray and itemize the list of possible + // catch() commands that could have caught this so we can find the list of its superclasses QString compatibleObjects; for (int catchTypeIdx = 1; catchTypeIdx < pCatchableTypeArray->nCatchableTypes; catchTypeIdx++) { CatchableType_internal* pCatchableSuperclassType = reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[catchTypeIdx]); From d92bb16d0c3ea10d659b9dbce668696e8a283599 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Wed, 17 Jun 2020 00:58:57 -0700 Subject: [PATCH 012/388] replacing the use of std::mutex with a spinlock, which we can try to lock but exit if the attempt times out. --- interface/src/CrashHandler_Crashpad.cpp | 119 +++++++++++++++++++++--- 1 file changed, 104 insertions(+), 15 deletions(-) diff --git a/interface/src/CrashHandler_Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp index 2ddcd3f398..b11a3e119e 100644 --- a/interface/src/CrashHandler_Crashpad.cpp +++ b/interface/src/CrashHandler_Crashpad.cpp @@ -15,16 +15,15 @@ #include -#include #include #include -#include +#include +#include #include #include #include - #if defined(__clang__) #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wc++14-extensions" @@ -33,7 +32,6 @@ #include #include #include -#include #include #if defined(__clang__) @@ -44,16 +42,96 @@ #include #include -using namespace crashpad; - static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; -static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; -static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; +// ------------------------------------------------------------------------------------------------ +// SpinLock - a lock that can timeout attempting to lock a block of code, and is in a busy-wait cycle while trying to acquire +// note that this code will malfunction if you attempt to grab a lock while already holding it -CrashpadClient* client { nullptr }; -std::mutex annotationMutex; +class SpinLock { +public: + SpinLock(); + void lock(); + bool lock(int msecs); + void unlock(); +private: + QAtomicInteger _lock{ 0 }; + + Q_DISABLE_COPY(SpinLock) +}; + +class SpinLockLocker { +public: + SpinLockLocker(SpinLock& lock, int msecs = -1); + ~SpinLockLocker(); + bool isLocked() const; + void unlock(); + bool relock(int msecs = -1); + +private: + SpinLock* _lock; + bool _isLocked; + + Q_DISABLE_COPY(SpinLockLocker) +}; + +SpinLock::SpinLock() { +} + +void SpinLock::lock() { + while (!_lock.testAndSetAcquire(0, 1)) + ; +} + +bool SpinLock::lock(int msecs) { + QDeadlineTimer deadline(msecs); + for (;;) { + if (_lock.testAndSetAcquire(0, 1)) { + return true; + } + if (deadline.hasExpired()) { + return false; + } + } +} + +void SpinLock::unlock() { + _lock.storeRelease(0); +} + +SpinLockLocker::SpinLockLocker(SpinLock& lock, int msecs /* = -1 */ ) : _lock(&lock) { + _isLocked = _lock->lock(msecs); +} + +SpinLockLocker::~SpinLockLocker() { + if (_isLocked) { + _lock->unlock(); + } +} + +bool SpinLockLocker::isLocked() const { + return _isLocked; +} + +void SpinLockLocker::unlock() { + if (_isLocked) { + _lock->unlock(); + _isLocked = false; + } +} + +bool SpinLockLocker::relock(int msecs /* = -1 */ ) { + if (!_isLocked) { + _isLocked = _lock->lock(msecs); + } + return _isLocked; +} + +// ------------------------------------------------------------------------------------------------ + +crashpad::CrashpadClient* client{ nullptr }; +SpinLock crashpadAnnotationsProtect; crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; #if defined(Q_OS_WIN) @@ -63,12 +141,18 @@ static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler" }; #endif #ifdef Q_OS_WIN +// ------------------------------------------------------------------------------------------------ +// The area within this #ifdef is specific to the Microsoft C++ compiler + +static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; +static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; +static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds + #include #include void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); - LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { if (!client) { return EXCEPTION_CONTINUE_SEARCH; @@ -123,6 +207,11 @@ struct CatchableTypeArray_internal { // the underlying C++ exception type (or at least its name) before throwing the whole // mess at crashpad void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { + SpinLockLocker guard(crashpadAnnotationsProtect, ANNOTATION_LOCK_WEAK_ATTEMPT); + if (!guard.isLocked()) { + return; + } + PEXCEPTION_RECORD ExceptionRecord = pExceptionInfo->ExceptionRecord; /* Exception arguments for Microsoft C++ exceptions: @@ -160,8 +249,6 @@ void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { } const std::type_info* type = reinterpret_cast(moduleBase + pCatchableType->pType); - // we're crashing, not really sure it matters who's currently holding the lock (although this could in theory really mess things up - annotationMutex.try_lock(); crashpadAnnotations->SetKeyValue("thrownObject", type->name()); // After annotating the name of the actual object type, go through the other entries in CatcahleTypeArray and itemize the list of possible @@ -182,6 +269,8 @@ void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { crashpadAnnotations->SetKeyValue("thrownObjectLike", compatibleObjects.toStdString()); } +// End of code specific to the Microsoft C++ compiler +// ------------------------------------------------------------------------------------------------ #endif bool startCrashHandler(std::string appPath) { @@ -190,7 +279,7 @@ bool startCrashHandler(std::string appPath) { } assert(!client); - client = new CrashpadClient(); + client = new crashpad::CrashpadClient(); std::vector arguments; std::map annotations; @@ -243,7 +332,7 @@ bool startCrashHandler(std::string appPath) { void setCrashAnnotation(std::string name, std::string value) { if (client) { - std::lock_guard guard(annotationMutex); + SpinLockLocker guard(crashpadAnnotationsProtect); if (!crashpadAnnotations) { crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); From b6e1c9e3afd97a6e0722d6296916be80a9ef8417 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Fri, 19 Jun 2020 18:26:26 -0700 Subject: [PATCH 013/388] added "c++ crash" to crash menu --- interface/src/Menu.cpp | 7 ++++++- interface/src/Menu.h | 2 ++ libraries/shared/src/CrashHelpers.cpp | 10 +++++++--- libraries/shared/src/CrashHelpers.h | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index a1bb670837..bbb71cbea3 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -705,7 +705,7 @@ Menu::Menu() { // Developer > Crash >>> bool result = false; const QString HIFI_SHOW_DEVELOPER_CRASH_MENU("HIFI_SHOW_DEVELOPER_CRASH_MENU"); - result = QProcessEnvironment::systemEnvironment().contains(HIFI_SHOW_DEVELOPER_CRASH_MENU); + result = true;//QProcessEnvironment::systemEnvironment().contains(HIFI_SHOW_DEVELOPER_CRASH_MENU); if (result) { MenuWrapper* crashMenu = developerMenu->addMenu("Crash"); @@ -745,6 +745,11 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashThrownException); + connect(action, &QAction::triggered, qApp, []() { crash::throwException(); }); + action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashThrownExceptionThreaded); + connect(action, &QAction::triggered, qApp, []() { std::thread(crash::throwException).join(); }); + addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOnShutdown, 0, qApp, SLOT(crashOnShutdown())); } diff --git a/interface/src/Menu.h b/interface/src/Menu.h index a3da179ad3..d33b3b0f5e 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -77,6 +77,8 @@ namespace MenuOption { const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)"; const QString CrashNewFault = "New Fault"; const QString CrashNewFaultThreaded = "New Fault (threaded)"; + const QString CrashThrownException = "Thrown C++ exception"; + const QString CrashThrownExceptionThreaded = "Thrown C++ exception (threaded)"; const QString CreateEntitiesGrabbable = "Create Entities As Grabbable (except Zones, Particles, and Lights)"; const QString DeadlockInterface = "Deadlock Interface"; const QString UnresponsiveInterface = "Unresponsive Interface"; diff --git a/libraries/shared/src/CrashHelpers.cpp b/libraries/shared/src/CrashHelpers.cpp index 1676318f3e..8a1a6df1d2 100644 --- a/libraries/shared/src/CrashHelpers.cpp +++ b/libraries/shared/src/CrashHelpers.cpp @@ -19,7 +19,7 @@ #else #include #endif - +#include namespace crash { @@ -86,7 +86,11 @@ void newFault() { const size_t GIGABYTE = 1024 * 1024 * 1024; new char[GIGABYTE]; } +} + +void throwException() { + qCDebug(shared) << "About to throw an exception"; + throw std::runtime_error("unexpected exception"); +} } - -} diff --git a/libraries/shared/src/CrashHelpers.h b/libraries/shared/src/CrashHelpers.h index 247aea5cde..ccda319819 100644 --- a/libraries/shared/src/CrashHelpers.h +++ b/libraries/shared/src/CrashHelpers.h @@ -25,6 +25,7 @@ void nullDeref(); void doAbort(); void outOfBoundsVectorCrash(); void newFault(); +void throwException(); } From 033726fc644fc77e25dc3b08fa8cfbd183c39f9e Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Fri, 19 Jun 2020 18:28:01 -0700 Subject: [PATCH 014/388] realizing the current logic is catching first-chance exceptions --- interface/src/CrashHandler_Crashpad.cpp | 31 +++++++++++++++++-------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/interface/src/CrashHandler_Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp index b11a3e119e..89984cd398 100644 --- a/interface/src/CrashHandler_Crashpad.cpp +++ b/interface/src/CrashHandler_Crashpad.cpp @@ -148,26 +148,32 @@ static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds +LPTOP_LEVEL_EXCEPTION_FILTER gl_nextUnhandledExceptionFilter = nullptr; + #include #include void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); -LONG WINAPI vectoredExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { - if (!client) { - return EXCEPTION_CONTINUE_SEARCH; - } - - if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || - pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN) { +LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + if (client && (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || + pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN)) { client->DumpAndCrash(pExceptionInfo); } - if (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_MSVC_CPP_EXCEPTION) { + return EXCEPTION_CONTINUE_SEARCH; +} + +LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + if (client && pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_MSVC_CPP_EXCEPTION) { fatalCxxException(pExceptionInfo); client->DumpAndCrash(pExceptionInfo); } + if (gl_nextUnhandledExceptionFilter != nullptr) { + return gl_nextUnhandledExceptionFilter(pExceptionInfo); + } + return EXCEPTION_CONTINUE_SEARCH; } @@ -323,11 +329,16 @@ bool startCrashHandler(std::string appPath) { // Enable automated uploads. database->GetSettings()->SetUploadsEnabled(true); + if (!client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true)) { + return false; + } + #ifdef Q_OS_WIN - AddVectoredExceptionHandler(0, vectoredExceptionHandler); + AddVectoredExceptionHandler(0, firstChanceExceptionHandler); + gl_nextUnhandledExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); #endif - return client->StartHandler(handler, db, db, BACKTRACE_URL, annotations, arguments, true, true); + return true; } void setCrashAnnotation(std::string name, std::string value) { From af6d2b2e567cae91d277cec484280e7262c33798 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Sat, 20 Jun 2020 20:54:24 -0700 Subject: [PATCH 015/388] added timer to detect if the unhandle exception hook has been broken added internal documentation on the structures and functions involved --- interface/src/CrashHandler.h | 2 + interface/src/CrashHandler_Breakpad.cpp | 3 + interface/src/CrashHandler_Crashpad.cpp | 78 +++++++++++++++++++------ interface/src/CrashHandler_None.cpp | 3 + interface/src/main.cpp | 1 + 5 files changed, 69 insertions(+), 18 deletions(-) diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index 6f8e9c3bf6..5b7e3b4438 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -13,8 +13,10 @@ #define hifi_CrashHandler_h #include +class QCoreApplication; bool startCrashHandler(std::string appPath); void setCrashAnnotation(std::string name, std::string value); +void startCrashHookMonitor(QCoreApplication* app); #endif // hifi_CrashHandler_h diff --git a/interface/src/CrashHandler_Breakpad.cpp b/interface/src/CrashHandler_Breakpad.cpp index c21bfa95e0..839e99ad3d 100644 --- a/interface/src/CrashHandler_Breakpad.cpp +++ b/interface/src/CrashHandler_Breakpad.cpp @@ -81,4 +81,7 @@ void setCrashAnnotation(std::string name, std::string value) { flushAnnotations(); } +void startCrashHookMonitor(QCoreApplication* app) { +} + #endif diff --git a/interface/src/CrashHandler_Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp index 89984cd398..9671facf17 100644 --- a/interface/src/CrashHandler_Crashpad.cpp +++ b/interface/src/CrashHandler_Crashpad.cpp @@ -144,18 +144,27 @@ static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler" }; // ------------------------------------------------------------------------------------------------ // The area within this #ifdef is specific to the Microsoft C++ compiler -static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; -static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; -static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds - -LPTOP_LEVEL_EXCEPTION_FILTER gl_nextUnhandledExceptionFilter = nullptr; +#include +#include +#include +#include #include #include -void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); +static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; +static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; +static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds -LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { +LPTOP_LEVEL_EXCEPTION_FILTER gl_crashpadUnhandledExceptionFilter = nullptr; +QTimer unhandledExceptionTimer; // checks occasionally in case loading an external DLL reset the unhandled exception pointer + +void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); // extracts type information from a thrown C++ exception +LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any thrown exception (whether or not it's caught) +LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any exception without a corresponding catch + +static LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { + // we're catching these exceptions on first-chance as the system state is corrupted at this point and they may not survive the exception handling mechanism if (client && (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN)) { client->DumpAndCrash(pExceptionInfo); @@ -164,35 +173,41 @@ LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { return EXCEPTION_CONTINUE_SEARCH; } -LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { +static LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { if (client && pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_MSVC_CPP_EXCEPTION) { fatalCxxException(pExceptionInfo); client->DumpAndCrash(pExceptionInfo); } - if (gl_nextUnhandledExceptionFilter != nullptr) { - return gl_nextUnhandledExceptionFilter(pExceptionInfo); + if (gl_crashpadUnhandledExceptionFilter != nullptr) { + return gl_crashpadUnhandledExceptionFilter(pExceptionInfo); } return EXCEPTION_CONTINUE_SEARCH; } +// The following structures are modified versions of structs defined inplicitly by the Microsoft C++ compiler +// as described at http://www.geoffchappell.com/studies/msvc/language/predefined/ +// They are redefined here as the definitions the compiler gives only work in 32-bit contexts and are out-of-sync +// with the internal structures when operating in a 64-bit environment +// as discovered and described here: https://stackoverflow.com/questions/39113168/c-rtti-in-a-windows-64-bit-vectoredexceptionhandler-ms-visual-studio-2015 + #pragma pack(push, ehdata, 4) -struct PMD_internal { +struct PMD_internal { // internal name: _PMD (no changes, so could in theory just use the original) int mdisp; int pdisp; int vdisp; }; -struct ThrowInfo_internal { +struct ThrowInfo_internal { // internal name: _ThrowInfo (changed all pointers into __int32) __int32 attributes; __int32 pmfnUnwind; // 32-bit RVA __int32 pForwardCompat; // 32-bit RVA __int32 pCatchableTypeArray; // 32-bit RVA }; -struct CatchableType_internal { +struct CatchableType_internal { // internal name: _CatchableType (changed all pointers into __int32) __int32 properties; __int32 pType; // 32-bit RVA PMD_internal thisDisplacement; @@ -201,7 +216,7 @@ struct CatchableType_internal { }; #pragma warning(disable : 4200) -struct CatchableTypeArray_internal { +struct CatchableTypeArray_internal { // internal name: _CatchableTypeArray (changed all pointers into __int32) int nCatchableTypes; __int32 arrayOfCatchableTypes[0]; // 32-bit RVA }; @@ -212,7 +227,12 @@ struct CatchableTypeArray_internal { // everything inside this function is extremely undocumented, attempting to extract // the underlying C++ exception type (or at least its name) before throwing the whole // mess at crashpad -void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { +// Some links describing how C++ exception handling works in an SEH context +// (since C++ exceptions are a figment of the Microsoft compiler): +// - https://www.codeproject.com/Articles/175482/Compiler-Internals-How-Try-Catch-Throw-are-Interpr +// - https://stackoverflow.com/questions/21888076/how-to-find-the-context-record-for-user-mode-exception-on-x64 + +static void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { SpinLockLocker guard(crashpadAnnotationsProtect, ANNOTATION_LOCK_WEAK_ATTEMPT); if (!guard.isLocked()) { return; @@ -275,9 +295,17 @@ void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { crashpadAnnotations->SetKeyValue("thrownObjectLike", compatibleObjects.toStdString()); } +void checkUnhandledExceptionHook() { + LPTOP_LEVEL_EXCEPTION_FILTER prevExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); + if (prevExceptionFilter != unhandledExceptionHandler) { + qWarning() << QString("Restored unhandled exception filter (which had been changed to %1)") + .arg(reinterpret_cast(prevExceptionFilter), 16, 16, QChar('0')); + } +} + // End of code specific to the Microsoft C++ compiler // ------------------------------------------------------------------------------------------------ -#endif +#endif // Q_OS_WIN bool startCrashHandler(std::string appPath) { if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { @@ -335,7 +363,7 @@ bool startCrashHandler(std::string appPath) { #ifdef Q_OS_WIN AddVectoredExceptionHandler(0, firstChanceExceptionHandler); - gl_nextUnhandledExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); + gl_crashpadUnhandledExceptionFilter = SetUnhandledExceptionFilter(unhandledExceptionHandler); #endif return true; @@ -354,4 +382,18 @@ void setCrashAnnotation(std::string name, std::string value) { } } -#endif +void startCrashHookMonitor(QCoreApplication* app) { +#ifdef Q_OS_WIN + // create a timer that checks to see if our exception handler has been reset. This may occur when a new CRT + // is initialized, which could happen any time a DLL not compiled with the same compiler is loaded. + // It would be nice if this were replaced with a more intelligent response; this fires once a minute which + // may be too much (extra code running) and too little (leaving up to a 1min gap after the hook is broken) + checkUnhandledExceptionHook(); + + unhandledExceptionTimer.moveToThread(app->thread()); + QObject::connect(&unhandledExceptionTimer, &QTimer::timeout, checkUnhandledExceptionHook); + unhandledExceptionTimer.start(60000); +#endif // Q_OS_WIN +} + +#endif // HAS_CRASHPAD diff --git a/interface/src/CrashHandler_None.cpp b/interface/src/CrashHandler_None.cpp index 77b8ab332e..4fe56cd042 100644 --- a/interface/src/CrashHandler_None.cpp +++ b/interface/src/CrashHandler_None.cpp @@ -25,4 +25,7 @@ bool startCrashHandler(std::string appPath) { void setCrashAnnotation(std::string name, std::string value) { } +void startCrashHookMonitor(QCoreApplication* app) { +} + #endif diff --git a/interface/src/main.cpp b/interface/src/main.cpp index ae476b8142..bdbafbaeb8 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -381,6 +381,7 @@ int main(int argc, const char* argv[]) { #if defined(Q_OS_LINUX) app.setWindowIcon(QIcon(PathUtils::resourcesPath() + "images/vircadia-logo.svg")); #endif + startCrashHookMonitor(&app); QTimer exitTimer; if (traceDuration > 0.0f) { From f1d731da11655f1b64b2950608e90d9c012afb5b Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 30 Jun 2020 00:25:27 -0400 Subject: [PATCH 016/388] Add users to the export. --- domain-server/src/DomainMetadata.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainMetadata.cpp b/domain-server/src/DomainMetadata.cpp index e30cefa1db..f8d8d2d989 100644 --- a/domain-server/src/DomainMetadata.cpp +++ b/domain-server/src/DomainMetadata.cpp @@ -211,7 +211,10 @@ void DomainMetadata::sendDescriptors() { } bool DomainMetadata::handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) { - QString domainMetadataJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(get(DESCRIPTORS)).toJson(QJsonDocument::Compact))); + QJsonArray metadataArray; + metadataArray << get(DESCRIPTORS); + metadataArray << get(USERS); + QString domainMetadataJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(metadataArray).toJson(QJsonDocument::Compact))); const QString URI_METADATA = "/metadata"; const QString EXPORTER_MIME_TYPE = "application/json"; From a7861855a7428455cca39b5e783bad0b9ee41cea Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Thu, 16 Jul 2020 14:34:02 -0700 Subject: [PATCH 017/388] properties update over wire --- libraries/entities/src/EntityItem.cpp | 4 ++++ libraries/entities/src/GizmoEntityItem.cpp | 15 ++---------- libraries/entities/src/GizmoEntityItem.h | 2 +- libraries/entities/src/GridEntityItem.cpp | 15 ++---------- libraries/entities/src/GridEntityItem.h | 2 +- libraries/entities/src/ImageEntityItem.cpp | 15 ++---------- libraries/entities/src/ImageEntityItem.h | 2 +- libraries/entities/src/LightEntityItem.cpp | 19 +-------------- libraries/entities/src/LightEntityItem.h | 4 +--- libraries/entities/src/LineEntityItem.cpp | 14 +---------- libraries/entities/src/LineEntityItem.h | 2 +- libraries/entities/src/MaterialEntityItem.cpp | 15 ++---------- libraries/entities/src/MaterialEntityItem.h | 2 +- libraries/entities/src/ModelEntityItem.cpp | 15 +----------- libraries/entities/src/ModelEntityItem.h | 2 +- .../entities/src/ParticleEffectEntityItem.cpp | 15 ++---------- .../entities/src/ParticleEffectEntityItem.h | 2 +- libraries/entities/src/PolyLineEntityItem.cpp | 14 +---------- libraries/entities/src/PolyLineEntityItem.h | 2 +- libraries/entities/src/PolyVoxEntityItem.cpp | 16 +++---------- libraries/entities/src/PolyVoxEntityItem.h | 2 +- libraries/entities/src/ShapeEntityItem.cpp | 15 ++---------- libraries/entities/src/ShapeEntityItem.h | 2 +- libraries/entities/src/TextEntityItem.cpp | 15 +----------- libraries/entities/src/TextEntityItem.h | 2 +- libraries/entities/src/WebEntityItem.cpp | 15 +----------- libraries/entities/src/WebEntityItem.h | 2 +- libraries/entities/src/ZoneEntityItem.cpp | 23 ++----------------- libraries/entities/src/ZoneEntityItem.h | 1 - 29 files changed, 40 insertions(+), 214 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 69f1ed2aee..faf29ec20b 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1001,6 +1001,9 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef element->getTree()->trackIncomingEntityLastEdited(lastEditedFromBufferAdjusted, bytesRead); } + if (somethingChanged) { + somethingChangedNotification(); + } return bytesRead; } @@ -1576,6 +1579,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { // when position and/or velocity was changed). _lastSimulated = now; } + somethingChangedNotification(); } // timestamps diff --git a/libraries/entities/src/GizmoEntityItem.cpp b/libraries/entities/src/GizmoEntityItem.cpp index 9d79e06534..47c9afd168 100644 --- a/libraries/entities/src/GizmoEntityItem.cpp +++ b/libraries/entities/src/GizmoEntityItem.cpp @@ -39,8 +39,8 @@ EntityItemProperties GizmoEntityItem::getProperties(const EntityPropertyFlags& d return properties; } -bool GizmoEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class +bool GizmoEntityItem::setSubClassProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(gizmoType, setGizmoType); withWriteLock([&] { @@ -49,17 +49,6 @@ bool GizmoEntityItem::setProperties(const EntityItemProperties& properties) { _needsRenderUpdate |= ringPropertiesChanged; }); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "GizmoEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/GizmoEntityItem.h b/libraries/entities/src/GizmoEntityItem.h index aa338355b8..37a802387d 100644 --- a/libraries/entities/src/GizmoEntityItem.h +++ b/libraries/entities/src/GizmoEntityItem.h @@ -26,7 +26,7 @@ public: // methods for getting/setting all properties of an entity EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - bool setProperties(const EntityItemProperties& properties) override; + bool setSubClassProperties(const EntityItemProperties& properties) override; EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/GridEntityItem.cpp b/libraries/entities/src/GridEntityItem.cpp index a1147b21df..e635511bfc 100644 --- a/libraries/entities/src/GridEntityItem.cpp +++ b/libraries/entities/src/GridEntityItem.cpp @@ -46,8 +46,8 @@ EntityItemProperties GridEntityItem::getProperties(const EntityPropertyFlags& de return properties; } -bool GridEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class +bool GridEntityItem::setSubClassProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); @@ -61,17 +61,6 @@ bool GridEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(majorGridEvery, setMajorGridEvery); SET_ENTITY_PROPERTY_FROM_PROPERTIES(minorGridEvery, setMinorGridEvery); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "GridEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/GridEntityItem.h b/libraries/entities/src/GridEntityItem.h index 165d9b50f5..7dc7a475b2 100644 --- a/libraries/entities/src/GridEntityItem.h +++ b/libraries/entities/src/GridEntityItem.h @@ -26,7 +26,7 @@ public: // methods for getting/setting all properties of an entity EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - bool setProperties(const EntityItemProperties& properties) override; + bool setSubClassProperties(const EntityItemProperties& properties) override; EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/ImageEntityItem.cpp b/libraries/entities/src/ImageEntityItem.cpp index c538ce619d..6a8c457b0a 100644 --- a/libraries/entities/src/ImageEntityItem.cpp +++ b/libraries/entities/src/ImageEntityItem.cpp @@ -45,8 +45,8 @@ EntityItemProperties ImageEntityItem::getProperties(const EntityPropertyFlags& d return properties; } -bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class +bool ImageEntityItem::setSubClassProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); @@ -62,17 +62,6 @@ bool ImageEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(keepAspectRatio, setKeepAspectRatio); SET_ENTITY_PROPERTY_FROM_PROPERTIES(subImage, setSubImage); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "ImageEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/ImageEntityItem.h b/libraries/entities/src/ImageEntityItem.h index a1be5a0554..bca67dc738 100644 --- a/libraries/entities/src/ImageEntityItem.h +++ b/libraries/entities/src/ImageEntityItem.h @@ -26,7 +26,7 @@ public: // methods for getting/setting all properties of an entity EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - bool setProperties(const EntityItemProperties& properties) override; + bool setSubClassProperties(const EntityItemProperties& properties) override; EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/LightEntityItem.cpp b/libraries/entities/src/LightEntityItem.cpp index 11864f59da..715b457bde 100644 --- a/libraries/entities/src/LightEntityItem.cpp +++ b/libraries/entities/src/LightEntityItem.cpp @@ -119,24 +119,8 @@ void LightEntityItem::setCutoff(float value) { } } -bool LightEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "LightEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - somethingChangedNotification(); - } - return somethingChanged; -} - bool LightEntityItem::setSubClassProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setSubClassProperties(properties); // set the properties in our base class + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(isSpotlight, setIsSpotlight); @@ -148,7 +132,6 @@ bool LightEntityItem::setSubClassProperties(const EntityItemProperties& properti return somethingChanged; } - int LightEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args, EntityPropertyFlags& propertyFlags, bool overwriteLocalData, diff --git a/libraries/entities/src/LightEntityItem.h b/libraries/entities/src/LightEntityItem.h index 533d2994d6..5245770ec8 100644 --- a/libraries/entities/src/LightEntityItem.h +++ b/libraries/entities/src/LightEntityItem.h @@ -33,11 +33,9 @@ public: /// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately virtual void setUnscaledDimensions(const glm::vec3& value) override; - virtual bool setProperties(const EntityItemProperties& properties) override; - virtual bool setSubClassProperties(const EntityItemProperties& properties) override; - // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/LineEntityItem.cpp b/libraries/entities/src/LineEntityItem.cpp index 45cdf980a1..f3304e716f 100644 --- a/libraries/entities/src/LineEntityItem.cpp +++ b/libraries/entities/src/LineEntityItem.cpp @@ -45,24 +45,12 @@ EntityItemProperties LineEntityItem::getProperties(const EntityPropertyFlags& de return properties; } -bool LineEntityItem::setProperties(const EntityItemProperties& properties) { +bool LineEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(linePoints, setLinePoints); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "LineEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/LineEntityItem.h b/libraries/entities/src/LineEntityItem.h index ee473225fe..38e526204e 100644 --- a/libraries/entities/src/LineEntityItem.h +++ b/libraries/entities/src/LineEntityItem.h @@ -24,7 +24,7 @@ class LineEntityItem : public EntityItem { // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/MaterialEntityItem.cpp b/libraries/entities/src/MaterialEntityItem.cpp index 11745c29ce..dcdd80cb48 100644 --- a/libraries/entities/src/MaterialEntityItem.cpp +++ b/libraries/entities/src/MaterialEntityItem.cpp @@ -38,8 +38,8 @@ EntityItemProperties MaterialEntityItem::getProperties(const EntityPropertyFlags return properties; } -bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class +bool MaterialEntityItem::setSubClassProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialURL, setMaterialURL); SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialMappingMode, setMaterialMappingMode); @@ -51,17 +51,6 @@ bool MaterialEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialData, setMaterialData); SET_ENTITY_PROPERTY_FROM_PROPERTIES(materialRepeat, setMaterialRepeat); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "MaterialEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/MaterialEntityItem.h b/libraries/entities/src/MaterialEntityItem.h index d8de8c3bc6..76cfbfa9dc 100644 --- a/libraries/entities/src/MaterialEntityItem.h +++ b/libraries/entities/src/MaterialEntityItem.h @@ -24,7 +24,7 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp index 6818c5cb6a..38e7ad9c3a 100644 --- a/libraries/entities/src/ModelEntityItem.cpp +++ b/libraries/entities/src/ModelEntityItem.cpp @@ -79,9 +79,8 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d return properties; } -bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { +bool ModelEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); @@ -105,18 +104,6 @@ bool ModelEntityItem::setProperties(const EntityItemProperties& properties) { somethingChanged = somethingChanged || somethingChangedInAnimations; }); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "ModelEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - somethingChangedNotification(); - } - return somethingChanged; } diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h index c331a94adf..795630a72a 100644 --- a/libraries/entities/src/ModelEntityItem.h +++ b/libraries/entities/src/ModelEntityItem.h @@ -29,7 +29,7 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/ParticleEffectEntityItem.cpp b/libraries/entities/src/ParticleEffectEntityItem.cpp index 082c4efb17..5ccc209a54 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.cpp +++ b/libraries/entities/src/ParticleEffectEntityItem.cpp @@ -696,8 +696,8 @@ EntityItemProperties ParticleEffectEntityItem::getProperties(const EntityPropert return properties; } -bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class +bool ParticleEffectEntityItem::setSubClassProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); @@ -750,17 +750,6 @@ bool ParticleEffectEntityItem::setProperties(const EntityItemProperties& propert SET_ENTITY_PROPERTY_FROM_PROPERTIES(spinFinish, setSpinFinish); SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotateWithEntity, setRotateWithEntity); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "ParticleEffectEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/ParticleEffectEntityItem.h b/libraries/entities/src/ParticleEffectEntityItem.h index 358e5a6b6d..c96323fc9a 100644 --- a/libraries/entities/src/ParticleEffectEntityItem.h +++ b/libraries/entities/src/ParticleEffectEntityItem.h @@ -214,7 +214,7 @@ public: // methods for getting/setting all properties of this entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/PolyLineEntityItem.cpp b/libraries/entities/src/PolyLineEntityItem.cpp index fcd4b6d89d..909bc132fb 100644 --- a/libraries/entities/src/PolyLineEntityItem.cpp +++ b/libraries/entities/src/PolyLineEntityItem.cpp @@ -53,9 +53,8 @@ EntityItemProperties PolyLineEntityItem::getProperties(const EntityPropertyFlags return properties; } -bool PolyLineEntityItem::setProperties(const EntityItemProperties& properties) { +bool PolyLineEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textures, setTextures); @@ -68,17 +67,6 @@ bool PolyLineEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(glow, setGlow); SET_ENTITY_PROPERTY_FROM_PROPERTIES(faceCamera, setFaceCamera); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "PolyLineEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/PolyLineEntityItem.h b/libraries/entities/src/PolyLineEntityItem.h index e68666d75e..8fb7831c6a 100644 --- a/libraries/entities/src/PolyLineEntityItem.h +++ b/libraries/entities/src/PolyLineEntityItem.h @@ -24,7 +24,7 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/PolyVoxEntityItem.cpp b/libraries/entities/src/PolyVoxEntityItem.cpp index c31d0b81b3..dd55ca898b 100644 --- a/libraries/entities/src/PolyVoxEntityItem.cpp +++ b/libraries/entities/src/PolyVoxEntityItem.cpp @@ -106,8 +106,9 @@ EntityItemProperties PolyVoxEntityItem::getProperties(const EntityPropertyFlags& return properties; } -bool PolyVoxEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class +bool PolyVoxEntityItem::setSubClassProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; + SET_ENTITY_PROPERTY_FROM_PROPERTIES(voxelVolumeSize, setVoxelVolumeSize); SET_ENTITY_PROPERTY_FROM_PROPERTIES(voxelData, setVoxelData); SET_ENTITY_PROPERTY_FROM_PROPERTIES(voxelSurfaceStyle, setVoxelSurfaceStyle); @@ -121,17 +122,6 @@ bool PolyVoxEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(yPNeighborID, setYPNeighborID); SET_ENTITY_PROPERTY_FROM_PROPERTIES(zPNeighborID, setZPNeighborID); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "PolyVoxEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/PolyVoxEntityItem.h b/libraries/entities/src/PolyVoxEntityItem.h index 9d02cbcdad..f994fcd37c 100644 --- a/libraries/entities/src/PolyVoxEntityItem.h +++ b/libraries/entities/src/PolyVoxEntityItem.h @@ -24,7 +24,7 @@ class PolyVoxEntityItem : public EntityItem { // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/ShapeEntityItem.cpp b/libraries/entities/src/ShapeEntityItem.cpp index 5b9c3e6881..5e140665b3 100644 --- a/libraries/entities/src/ShapeEntityItem.cpp +++ b/libraries/entities/src/ShapeEntityItem.cpp @@ -165,8 +165,8 @@ entity::Shape ShapeEntityItem::getShape() const { }); } -bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class +bool ShapeEntityItem::setSubClassProperties(const EntityItemProperties& properties) { + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); @@ -177,17 +177,6 @@ bool ShapeEntityItem::setProperties(const EntityItemProperties& properties) { }); SET_ENTITY_PROPERTY_FROM_PROPERTIES(shape, setShape); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "ShapeEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties.getLastEdited()); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/ShapeEntityItem.h b/libraries/entities/src/ShapeEntityItem.h index 43c2ce2a1f..7320867430 100644 --- a/libraries/entities/src/ShapeEntityItem.h +++ b/libraries/entities/src/ShapeEntityItem.h @@ -55,7 +55,7 @@ public: // methods for getting/setting all properties of an entity EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - bool setProperties(const EntityItemProperties& properties) override; + bool setSubClassProperties(const EntityItemProperties& properties) override; EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/TextEntityItem.cpp b/libraries/entities/src/TextEntityItem.cpp index 9c53196247..a996319463 100644 --- a/libraries/entities/src/TextEntityItem.cpp +++ b/libraries/entities/src/TextEntityItem.cpp @@ -73,9 +73,8 @@ EntityItemProperties TextEntityItem::getProperties(const EntityPropertyFlags& de return properties; } -bool TextEntityItem::setProperties(const EntityItemProperties& properties) { +bool TextEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class withWriteLock([&] { bool pulsePropertiesChanged = _pulseProperties.setProperties(properties); @@ -99,18 +98,6 @@ bool TextEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffect, setTextEffect); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectColor, setTextEffectColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(textEffectThickness, setTextEffectThickness); - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "TextEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - somethingChangedNotification(); - } return somethingChanged; } diff --git a/libraries/entities/src/TextEntityItem.h b/libraries/entities/src/TextEntityItem.h index 5e3f6d7c02..91496708f6 100644 --- a/libraries/entities/src/TextEntityItem.h +++ b/libraries/entities/src/TextEntityItem.h @@ -31,7 +31,7 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index d8b2c8fc3d..73e0096e76 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -63,9 +63,8 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des return properties; } -bool WebEntityItem::setProperties(const EntityItemProperties& properties) { +bool WebEntityItem::setSubClassProperties(const EntityItemProperties& properties) { bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor); SET_ENTITY_PROPERTY_FROM_PROPERTIES(alpha, setAlpha); @@ -83,18 +82,6 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(showKeyboardFocusHighlight, setShowKeyboardFocusHighlight); - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "WebEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - somethingChangedNotification(); - } - return somethingChanged; } diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index b61e2b124f..5ab53d6ef8 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -27,7 +27,7 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; + virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 842651fcac..88466caff0 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -76,27 +76,8 @@ EntityItemProperties ZoneEntityItem::getProperties(const EntityPropertyFlags& de return properties; } -bool ZoneEntityItem::setProperties(const EntityItemProperties& properties) { - bool somethingChanged = false; - somethingChanged = EntityItem::setProperties(properties); // set the properties in our base class - - if (somethingChanged) { - bool wantDebug = false; - if (wantDebug) { - uint64_t now = usecTimestampNow(); - int elapsed = now - getLastEdited(); - qCDebug(entities) << "ZoneEntityItem::setProperties() AFTER update... edited AGO=" << elapsed << - "now=" << now << " getLastEdited()=" << getLastEdited(); - } - setLastEdited(properties._lastEdited); - somethingChangedNotification(); - } - - return somethingChanged; -} - bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& properties) { - bool somethingChanged = EntityItem::setSubClassProperties(properties); // set the properties in our base class + bool somethingChanged = false; SET_ENTITY_PROPERTY_FROM_PROPERTIES(shapeType, setShapeType); SET_ENTITY_PROPERTY_FROM_PROPERTIES(compoundShapeURL, setCompoundShapeURL); @@ -122,7 +103,7 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); SET_ENTITY_PROPERTY_FROM_PROPERTIES(screenshare, setScreenshare); - somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || + somethingChanged |= _keyLightPropertiesChanged || _ambientLightPropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; return somethingChanged; diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 8ec3ea12b4..dda03f9115 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -33,7 +33,6 @@ public: // methods for getting/setting all properties of an entity virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override; - virtual bool setProperties(const EntityItemProperties& properties) override; virtual bool setSubClassProperties(const EntityItemProperties& properties) override; virtual EntityPropertyFlags getEntityProperties(EncodeBitstreamParams& params) const override; From 1ea78c6ab56272b3c94bca383fb4d7b1b2d29ee7 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Fri, 17 Jul 2020 05:22:59 -0400 Subject: [PATCH 018/388] Enable automatic avatar and audio mixer threading defaults --- domain-server/resources/describe-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 3ae92651b8..2adca55554 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -1075,7 +1075,7 @@ "label": "Automatically determine thread count", "type": "checkbox", "help": "Allow system to determine number of threads (recommended)", - "default": false, + "default": true, "advanced": true }, { @@ -1365,7 +1365,7 @@ "label": "Automatically determine thread count", "type": "checkbox", "help": "Allow system to determine number of threads (recommended)", - "default": false, + "default": true, "advanced": true }, { From 1c545dffca5355bbc8f04524fe000bc4d5cf9967 Mon Sep 17 00:00:00 2001 From: kasenvr <52365539+kasenvr@users.noreply.github.com> Date: Mon, 20 Jul 2020 14:25:52 -0400 Subject: [PATCH 019/388] Apply suggestions from code review Co-authored-by: David Rowe --- domain-server/resources/metadata_exporter/index.html | 4 ++-- domain-server/src/DomainMetadata.h | 3 +-- domain-server/src/DomainServer.cpp | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/domain-server/resources/metadata_exporter/index.html b/domain-server/resources/metadata_exporter/index.html index acc7aae23a..c75cc711e8 100644 --- a/domain-server/resources/metadata_exporter/index.html +++ b/domain-server/resources/metadata_exporter/index.html @@ -1,10 +1,10 @@ - Vircadia Metadata exporter + Vircadia Metadata Exporter -

Vircadia Metadata exporter

+

Vircadia Metadata Exporter

If you can see this page, this means that your domain's metadata is available to be exported.

diff --git a/domain-server/src/DomainMetadata.h b/domain-server/src/DomainMetadata.h index 6fae29248d..70f17348df 100644 --- a/domain-server/src/DomainMetadata.h +++ b/domain-server/src/DomainMetadata.h @@ -44,12 +44,11 @@ public: DomainMetadata(QObject* domainServer); ~DomainMetadata() = default; - // Get cached metadata QJsonObject get(); QJsonObject get(const QString& group); - bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false); + bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override; public slots: void descriptorsChanged(); diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 1d61449897..bad9c96e6b 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3079,14 +3079,14 @@ void DomainServer::initializeMetadataExporter() { int metadataExporterPort = _settingsManager.valueOrDefaultValueForKeyPath(EXPORTER_PORT).toInt(); if (metadataExporterPort < MIN_PORT || metadataExporterPort > MAX_PORT) { - qCWarning(domain_server) << "Metadata exporter port " << metadataExporterPort << " is out of range."; + qCWarning(domain_server) << "Metadata exporter port" << metadataExporterPort << "is out of range."; isMetadataExporterEnabled = false; } qCDebug(domain_server) << "Setting up Metadata exporter."; if (isMetadataExporterEnabled && !_httpMetadataExporterManager) { - qCInfo(domain_server) << "Starting Metadata exporter on port " << metadataExporterPort; + qCInfo(domain_server) << "Starting Metadata exporter on port" << metadataExporterPort; _httpMetadataExporterManager = new HTTPManager(QHostAddress::Any, (quint16)metadataExporterPort, QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), _metadata); } } From 8274ee3ca90c457aa82b83ac7004b171e611d770 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Mon, 20 Jul 2020 15:10:48 -0700 Subject: [PATCH 020/388] put back old last edited logic --- libraries/entities/src/EntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index faf29ec20b..ddedf0db18 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -1572,7 +1572,7 @@ bool EntityItem::setProperties(const EntityItemProperties& properties) { qCDebug(entities) << "EntityItem::setProperties() AFTER update... edited AGO=" << elapsed << "now=" << now << " getLastEdited()=" << getLastEdited(); #endif - setLastEdited(now); + setLastEdited(properties._lastEdited); if (getDirtyFlags() & (Simulation::DIRTY_TRANSFORM | Simulation::DIRTY_VELOCITIES)) { // anything that sets the transform or velocity must update _lastSimulated which is used // for kinematic extrapolation (e.g. we want to extrapolate forward from this moment From e8d9fe852e5996168630c7c42dc22b7b82f30460 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 21 Jul 2020 00:49:14 -0400 Subject: [PATCH 021/388] Adjust lines for length. --- domain-server/src/DomainServer.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index bad9c96e6b..d7b813dd7d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -3067,7 +3067,13 @@ void DomainServer::initializeExporter() { if (isExporterEnabled && !_httpExporterManager) { qCInfo(domain_server) << "Starting Prometheus exporter on port " << exporterPort; - _httpExporterManager = new HTTPManager(QHostAddress::Any, (quint16)exporterPort, QString("%1/resources/prometheus_exporter/").arg(QCoreApplication::applicationDirPath()), &_exporter); + _httpExporterManager = new HTTPManager + ( + QHostAddress::Any, + (quint16)exporterPort, + QString("%1/resources/prometheus_exporter/").arg(QCoreApplication::applicationDirPath()), + &_exporter + ); } } @@ -3087,7 +3093,13 @@ void DomainServer::initializeMetadataExporter() { if (isMetadataExporterEnabled && !_httpMetadataExporterManager) { qCInfo(domain_server) << "Starting Metadata exporter on port" << metadataExporterPort; - _httpMetadataExporterManager = new HTTPManager(QHostAddress::Any, (quint16)metadataExporterPort, QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), _metadata); + _httpMetadataExporterManager = new HTTPManager + ( + QHostAddress::Any, + (quint16)metadataExporterPort, + QString("%1/resources/metadata_exporter/").arg(QCoreApplication::applicationDirPath()), + _metadata + ); } } From eef1e3b93e3d04af82f23eca3e59d62af6010d5a Mon Sep 17 00:00:00 2001 From: kasenvr <52365539+kasenvr@users.noreply.github.com> Date: Tue, 21 Jul 2020 01:30:56 -0400 Subject: [PATCH 022/388] Update index.html --- domain-server/resources/metadata_exporter/index.html | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/domain-server/resources/metadata_exporter/index.html b/domain-server/resources/metadata_exporter/index.html index c75cc711e8..a22d50fe22 100644 --- a/domain-server/resources/metadata_exporter/index.html +++ b/domain-server/resources/metadata_exporter/index.html @@ -1,3 +1,15 @@ + + Vircadia Metadata Exporter From 2f00c3e0bd873fde622ee465e3b6fbc0b4f24bed Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Fri, 24 Jul 2020 21:32:22 -0700 Subject: [PATCH 023/388] minor review of formatting --- interface/src/CrashHandler_Crashpad.cpp | 77 +++++++++++++------------ 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/interface/src/CrashHandler_Crashpad.cpp b/interface/src/CrashHandler_Crashpad.cpp index 9671facf17..a8195ce6e8 100644 --- a/interface/src/CrashHandler_Crashpad.cpp +++ b/interface/src/CrashHandler_Crashpad.cpp @@ -42,8 +42,8 @@ #include #include -static const std::string BACKTRACE_URL { CMAKE_BACKTRACE_URL }; -static const std::string BACKTRACE_TOKEN { CMAKE_BACKTRACE_TOKEN }; +static const std::string BACKTRACE_URL{ CMAKE_BACKTRACE_URL }; +static const std::string BACKTRACE_TOKEN{ CMAKE_BACKTRACE_TOKEN }; // ------------------------------------------------------------------------------------------------ // SpinLock - a lock that can timeout attempting to lock a block of code, and is in a busy-wait cycle while trying to acquire @@ -55,6 +55,7 @@ public: void lock(); bool lock(int msecs); void unlock(); + private: QAtomicInteger _lock{ 0 }; @@ -132,12 +133,12 @@ bool SpinLockLocker::relock(int msecs /* = -1 */ ) { crashpad::CrashpadClient* client{ nullptr }; SpinLock crashpadAnnotationsProtect; -crashpad::SimpleStringDictionary* crashpadAnnotations { nullptr }; +crashpad::SimpleStringDictionary* crashpadAnnotations{ nullptr }; #if defined(Q_OS_WIN) -static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler.exe" }; +static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler.exe" }; #else -static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler" }; +static const QString CRASHPAD_HANDLER_NAME{ "crashpad_handler" }; #endif #ifdef Q_OS_WIN @@ -154,19 +155,19 @@ static const QString CRASHPAD_HANDLER_NAME { "crashpad_handler" }; static constexpr DWORD STATUS_MSVC_CPP_EXCEPTION = 0xE06D7363; static constexpr ULONG_PTR MSVC_CPP_EXCEPTION_SIGNATURE = 0x19930520; -static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds +static constexpr int ANNOTATION_LOCK_WEAK_ATTEMPT = 5000; // attempt to lock the annotations list, but give up if it takes more than 5 seconds LPTOP_LEVEL_EXCEPTION_FILTER gl_crashpadUnhandledExceptionFilter = nullptr; -QTimer unhandledExceptionTimer; // checks occasionally in case loading an external DLL reset the unhandled exception pointer +QTimer unhandledExceptionTimer; // checks occasionally in case loading an external DLL reset the unhandled exception pointer -void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); // extracts type information from a thrown C++ exception -LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any thrown exception (whether or not it's caught) -LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any exception without a corresponding catch +void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo); // extracts type information from a thrown C++ exception +LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any thrown exception (whether or not it's caught) +LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo); // called on any exception without a corresponding catch static LONG WINAPI firstChanceExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) { // we're catching these exceptions on first-chance as the system state is corrupted at this point and they may not survive the exception handling mechanism if (client && (pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_HEAP_CORRUPTION || - pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN)) { + pExceptionInfo->ExceptionRecord->ExceptionCode == STATUS_STACK_BUFFER_OVERRUN)) { client->DumpAndCrash(pExceptionInfo); } @@ -194,31 +195,31 @@ static LONG WINAPI unhandledExceptionHandler(PEXCEPTION_POINTERS pExceptionInfo) #pragma pack(push, ehdata, 4) -struct PMD_internal { // internal name: _PMD (no changes, so could in theory just use the original) +struct PMD_internal { // internal name: _PMD (no changes, so could in theory just use the original) int mdisp; int pdisp; int vdisp; }; -struct ThrowInfo_internal { // internal name: _ThrowInfo (changed all pointers into __int32) +struct ThrowInfo_internal { // internal name: _ThrowInfo (changed all pointers into __int32) __int32 attributes; __int32 pmfnUnwind; // 32-bit RVA __int32 pForwardCompat; // 32-bit RVA __int32 pCatchableTypeArray; // 32-bit RVA }; -struct CatchableType_internal { // internal name: _CatchableType (changed all pointers into __int32) +struct CatchableType_internal { // internal name: _CatchableType (changed all pointers into __int32) __int32 properties; - __int32 pType; // 32-bit RVA + __int32 pType; // 32-bit RVA PMD_internal thisDisplacement; __int32 sizeOrOffset; - __int32 copyFunction; // 32-bit RVA + __int32 copyFunction; // 32-bit RVA }; #pragma warning(disable : 4200) -struct CatchableTypeArray_internal { // internal name: _CatchableTypeArray (changed all pointers into __int32) +struct CatchableTypeArray_internal { // internal name: _CatchableTypeArray (changed all pointers into __int32) int nCatchableTypes; - __int32 arrayOfCatchableTypes[0]; // 32-bit RVA + __int32 arrayOfCatchableTypes[0]; // 32-bit RVA }; #pragma warning(default : 4200) @@ -255,23 +256,25 @@ static void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { // get the ThrowInfo struct from the exception arguments ThrowInfo_internal* pThrowInfo = reinterpret_cast(ExceptionRecord->ExceptionInformation[2]); ULONG_PTR moduleBase = ExceptionRecord->ExceptionInformation[3]; - if(moduleBase == 0 || pThrowInfo == NULL) { - return; // broken assumption + if (moduleBase == 0 || pThrowInfo == NULL) { + return; // broken assumption } // get the CatchableTypeArray* struct from ThrowInfo - if(pThrowInfo->pCatchableTypeArray == 0) { - return; // broken assumption + if (pThrowInfo->pCatchableTypeArray == 0) { + return; // broken assumption } - CatchableTypeArray_internal* pCatchableTypeArray = reinterpret_cast(moduleBase + pThrowInfo->pCatchableTypeArray); - if(pCatchableTypeArray->nCatchableTypes == 0 || pCatchableTypeArray->arrayOfCatchableTypes[0] == 0) { - return; // broken assumption + CatchableTypeArray_internal* pCatchableTypeArray = + reinterpret_cast(moduleBase + pThrowInfo->pCatchableTypeArray); + if (pCatchableTypeArray->nCatchableTypes == 0 || pCatchableTypeArray->arrayOfCatchableTypes[0] == 0) { + return; // broken assumption } // get the CatchableType struct for the actual exception type from CatchableTypeArray - CatchableType_internal* pCatchableType = reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[0]); - if(pCatchableType->pType == 0) { - return; // broken assumption + CatchableType_internal* pCatchableType = + reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[0]); + if (pCatchableType->pType == 0) { + return; // broken assumption } const std::type_info* type = reinterpret_cast(moduleBase + pCatchableType->pType); @@ -281,9 +284,10 @@ static void fatalCxxException(PEXCEPTION_POINTERS pExceptionInfo) { // catch() commands that could have caught this so we can find the list of its superclasses QString compatibleObjects; for (int catchTypeIdx = 1; catchTypeIdx < pCatchableTypeArray->nCatchableTypes; catchTypeIdx++) { - CatchableType_internal* pCatchableSuperclassType = reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[catchTypeIdx]); + CatchableType_internal* pCatchableSuperclassType = + reinterpret_cast(moduleBase + pCatchableTypeArray->arrayOfCatchableTypes[catchTypeIdx]); if (pCatchableSuperclassType->pType == 0) { - return; // broken assumption + return; // broken assumption } const std::type_info* superclassType = reinterpret_cast(moduleBase + pCatchableSuperclassType->pType); @@ -305,7 +309,7 @@ void checkUnhandledExceptionHook() { // End of code specific to the Microsoft C++ compiler // ------------------------------------------------------------------------------------------------ -#endif // Q_OS_WIN +#endif // Q_OS_WIN bool startCrashHandler(std::string appPath) { if (BACKTRACE_URL.empty() || BACKTRACE_TOKEN.empty()) { @@ -325,17 +329,16 @@ bool startCrashHandler(std::string appPath) { auto machineFingerPrint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); annotations["machine_fingerprint"] = machineFingerPrint.toStdString(); - arguments.push_back("--no-rate-limit"); // Setup Crashpad DB directory const auto crashpadDbName = "crashpad-db"; const auto crashpadDbDir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); - QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists + QDir(crashpadDbDir).mkpath(crashpadDbName); // Make sure the directory exists const auto crashpadDbPath = crashpadDbDir.toStdString() + "/" + crashpadDbName; // Locate Crashpad handler - const QFileInfo interfaceBinary { QString::fromStdString(appPath) }; + const QFileInfo interfaceBinary{ QString::fromStdString(appPath) }; const QDir interfaceDir = interfaceBinary.dir(); assert(interfaceDir.exists(CRASHPAD_HANDLER_NAME)); const std::string CRASHPAD_HANDLER_PATH = interfaceDir.filePath(CRASHPAD_HANDLER_NAME).toStdString(); @@ -373,7 +376,7 @@ void setCrashAnnotation(std::string name, std::string value) { if (client) { SpinLockLocker guard(crashpadAnnotationsProtect); if (!crashpadAnnotations) { - crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak + crashpadAnnotations = new crashpad::SimpleStringDictionary(); // don't free this, let it leak crashpad::CrashpadInfo* crashpad_info = crashpad::CrashpadInfo::GetCrashpadInfo(); crashpad_info->set_simple_annotations(crashpadAnnotations); } @@ -393,7 +396,7 @@ void startCrashHookMonitor(QCoreApplication* app) { unhandledExceptionTimer.moveToThread(app->thread()); QObject::connect(&unhandledExceptionTimer, &QTimer::timeout, checkUnhandledExceptionHook); unhandledExceptionTimer.start(60000); -#endif // Q_OS_WIN +#endif // Q_OS_WIN } -#endif // HAS_CRASHPAD +#endif // HAS_CRASHPAD From 61e6e8dfc9b39c85a4e2865012c1e47723717376 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Wed, 22 Apr 2020 23:16:51 -0700 Subject: [PATCH 024/388] Restructure ContextAwareProfile from a (thread-blocking) polling to a threadsafe-notification --- .../ui/src/ui/types/ContextAwareProfile.cpp | 65 +++++++++++++++---- .../ui/src/ui/types/ContextAwareProfile.h | 40 ++++++++++-- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index f74e8754c9..d5b8d958b6 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -20,20 +20,62 @@ static const QString RESTRICTED_FLAG_PROPERTY = "RestrictFileAccess"; -ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : - ContextAwareProfileParent(context), _context(context) { - assert(context); +QMutex RestrictedContextMonitor::gl_monitorMapProtect; +RestrictedContextMonitor::TMonitorMap RestrictedContextMonitor::gl_monitorMap; + +RestrictedContextMonitor::~RestrictedContextMonitor() { + gl_monitorMapProtect.lock(); + TMonitorMap::iterator lookup = gl_monitorMap.find(context); + if (lookup != gl_monitorMap.end()) { + gl_monitorMap.erase(lookup); + } + gl_monitorMapProtect.unlock(); } +RestrictedContextMonitor::TSharedPtr RestrictedContextMonitor::getMonitor(QQmlContext* context, bool createIfMissing) { + TSharedPtr monitor; + + gl_monitorMapProtect.lock(); + TMonitorMap::const_iterator lookup = gl_monitorMap.find(context); + if (lookup != gl_monitorMap.end()) { + monitor = lookup->second.lock(); + assert(monitor); + } else if(createIfMissing) { + monitor = std::make_shared(context); + monitor->selfPtr = monitor; + gl_monitorMap.insert(TMonitorMap::value_type(context, monitor)); + } + gl_monitorMapProtect.unlock(); + return monitor; +} + +ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : ContextAwareProfileParent(context), _context(context) { + assert(context); + + _monitor = RestrictedContextMonitor::getMonitor(context, true); + assert(_monitor); + connect(_monitor.get(), &RestrictedContextMonitor::onIsRestrictedChanged, this, &ContextAwareProfile::onIsRestrictedChanged); + if (_monitor->isUninitialized) { + _monitor->isRestricted = isRestrictedGetProperty(); + _monitor->isUninitialized = false; + } + _isRestricted.store(_monitor->isRestricted ? 1 : 0); +} void ContextAwareProfile::restrictContext(QQmlContext* context, bool restrict) { + RestrictedContextMonitor::TSharedPtr monitor = RestrictedContextMonitor::getMonitor(context, false); + context->setContextProperty(RESTRICTED_FLAG_PROPERTY, restrict); + if (monitor && monitor->isRestricted != restrict) { + monitor->isRestricted = restrict; + monitor->onIsRestrictedChanged(restrict); + } } -bool ContextAwareProfile::isRestrictedInternal() { +bool ContextAwareProfile::isRestrictedGetProperty() { if (QThread::currentThread() != thread()) { bool restrictedResult = false; - BLOCKING_INVOKE_METHOD(this, "isRestrictedInternal", Q_RETURN_ARG(bool, restrictedResult)); + BLOCKING_INVOKE_METHOD(this, "isRestrictedGetProperty", Q_RETURN_ARG(bool, restrictedResult)); return restrictedResult; } @@ -47,11 +89,10 @@ bool ContextAwareProfile::isRestrictedInternal() { return true; } -bool ContextAwareProfile::isRestricted() { - auto now = usecTimestampNow(); - if (now > _cacheExpiry) { - _cachedValue = isRestrictedInternal(); - _cacheExpiry = now + MAX_CACHE_AGE; - } - return _cachedValue; +void ContextAwareProfile::onIsRestrictedChanged(bool newValue) { + _isRestricted.store(newValue ? 1 : 0); +} + +bool ContextAwareProfile::isRestricted() { + return _isRestricted.load() != 0; } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h index 3192d2be54..30bdad4b78 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.h +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -12,6 +12,7 @@ #define hifi_ContextAwareProfile_h #include +#include #if !defined(Q_OS_ANDROID) #include @@ -30,12 +31,39 @@ using RequestInterceptorParent = QObject; class QQmlContext; +class RestrictedContextMonitor : public QObject { + Q_OBJECT +public: + typedef std::shared_ptr TSharedPtr; + typedef std::weak_ptr TWeakPtr; + + inline RestrictedContextMonitor(QQmlContext* c) : context(c) {} + ~RestrictedContextMonitor(); + + static TSharedPtr getMonitor(QQmlContext* context, bool createIfMissing); + +signals: + void onIsRestrictedChanged(bool newValue); + +public: + TWeakPtr selfPtr; + QQmlContext* context{ nullptr }; + bool isRestricted{ true }; + bool isUninitialized{ true }; + +private: + typedef std::map TMonitorMap; + + static QMutex gl_monitorMapProtect; + static TMonitorMap gl_monitorMap; +}; + class ContextAwareProfile : public ContextAwareProfileParent { Q_OBJECT public: static void restrictContext(QQmlContext* context, bool restrict = true); bool isRestricted(); - Q_INVOKABLE bool isRestrictedInternal(); + Q_INVOKABLE bool isRestrictedGetProperty(); protected: class RequestInterceptor : public RequestInterceptorParent { @@ -48,9 +76,13 @@ protected: ContextAwareProfile(QQmlContext* parent); QQmlContext* _context{ nullptr }; - bool _cachedValue{ false }; - quint64 _cacheExpiry{ 0 }; - constexpr static quint64 MAX_CACHE_AGE = MSECS_PER_SECOND; + QAtomicInt _isRestricted{ 0 }; + +private slots: + void onIsRestrictedChanged(bool newValue); + +private: + RestrictedContextMonitor::TSharedPtr _monitor; }; #endif // hifi_FileTypeProfile_h From 84a8c640e4f68fe9a9d7104a3acabdb53b5a22d8 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Fri, 15 May 2020 13:42:29 -0700 Subject: [PATCH 025/388] cleaned up variables names, replaced std::shared_ptr with QSharedPointer --- .../ui/src/ui/types/ContextAwareProfile.cpp | 24 +++++++++---------- .../ui/src/ui/types/ContextAwareProfile.h | 21 ++++++++-------- 2 files changed, 23 insertions(+), 22 deletions(-) diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index d5b8d958b6..0374de87ff 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -25,15 +25,15 @@ RestrictedContextMonitor::TMonitorMap RestrictedContextMonitor::gl_monitorMap; RestrictedContextMonitor::~RestrictedContextMonitor() { gl_monitorMapProtect.lock(); - TMonitorMap::iterator lookup = gl_monitorMap.find(context); + TMonitorMap::iterator lookup = gl_monitorMap.find(_context); if (lookup != gl_monitorMap.end()) { gl_monitorMap.erase(lookup); } gl_monitorMapProtect.unlock(); } -RestrictedContextMonitor::TSharedPtr RestrictedContextMonitor::getMonitor(QQmlContext* context, bool createIfMissing) { - TSharedPtr monitor; +RestrictedContextMonitor::TSharedPointer RestrictedContextMonitor::getMonitor(QQmlContext* context, bool createIfMissing) { + TSharedPointer monitor; gl_monitorMapProtect.lock(); TMonitorMap::const_iterator lookup = gl_monitorMap.find(context); @@ -41,8 +41,8 @@ RestrictedContextMonitor::TSharedPtr RestrictedContextMonitor::getMonitor(QQmlCo monitor = lookup->second.lock(); assert(monitor); } else if(createIfMissing) { - monitor = std::make_shared(context); - monitor->selfPtr = monitor; + monitor = TSharedPointer::create(context); + monitor->_selfPointer = monitor; gl_monitorMap.insert(TMonitorMap::value_type(context, monitor)); } gl_monitorMapProtect.unlock(); @@ -55,19 +55,19 @@ ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : ContextAwarePro _monitor = RestrictedContextMonitor::getMonitor(context, true); assert(_monitor); connect(_monitor.get(), &RestrictedContextMonitor::onIsRestrictedChanged, this, &ContextAwareProfile::onIsRestrictedChanged); - if (_monitor->isUninitialized) { - _monitor->isRestricted = isRestrictedGetProperty(); - _monitor->isUninitialized = false; + if (_monitor->_isUninitialized) { + _monitor->_isRestricted = isRestrictedGetProperty(); + _monitor->_isUninitialized = false; } - _isRestricted.store(_monitor->isRestricted ? 1 : 0); + _isRestricted.store(_monitor->_isRestricted ? 1 : 0); } void ContextAwareProfile::restrictContext(QQmlContext* context, bool restrict) { - RestrictedContextMonitor::TSharedPtr monitor = RestrictedContextMonitor::getMonitor(context, false); + RestrictedContextMonitor::TSharedPointer monitor = RestrictedContextMonitor::getMonitor(context, false); context->setContextProperty(RESTRICTED_FLAG_PROPERTY, restrict); - if (monitor && monitor->isRestricted != restrict) { - monitor->isRestricted = restrict; + if (monitor && monitor->_isRestricted != restrict) { + monitor->_isRestricted = restrict; monitor->onIsRestrictedChanged(restrict); } } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h index 30bdad4b78..ec0590eda5 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.h +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -13,6 +13,7 @@ #include #include +#include #if !defined(Q_OS_ANDROID) #include @@ -34,25 +35,25 @@ class QQmlContext; class RestrictedContextMonitor : public QObject { Q_OBJECT public: - typedef std::shared_ptr TSharedPtr; - typedef std::weak_ptr TWeakPtr; + typedef QSharedPointer TSharedPointer; + typedef QWeakPointer TWeakPointer; - inline RestrictedContextMonitor(QQmlContext* c) : context(c) {} + inline RestrictedContextMonitor(QQmlContext* c) : _context(c) {} ~RestrictedContextMonitor(); - static TSharedPtr getMonitor(QQmlContext* context, bool createIfMissing); + static TSharedPointer getMonitor(QQmlContext* context, bool createIfMissing); signals: void onIsRestrictedChanged(bool newValue); public: - TWeakPtr selfPtr; - QQmlContext* context{ nullptr }; - bool isRestricted{ true }; - bool isUninitialized{ true }; + TWeakPointer _selfPointer; + QQmlContext* _context{ nullptr }; + bool _isRestricted{ true }; + bool _isUninitialized{ true }; private: - typedef std::map TMonitorMap; + typedef std::map TMonitorMap; static QMutex gl_monitorMapProtect; static TMonitorMap gl_monitorMap; @@ -82,7 +83,7 @@ private slots: void onIsRestrictedChanged(bool newValue); private: - RestrictedContextMonitor::TSharedPtr _monitor; + RestrictedContextMonitor::TSharedPointer _monitor; }; #endif // hifi_FileTypeProfile_h From 3cff3f8b5deaba040c519adb8e43b619f5b55906 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Mon, 25 May 2020 15:51:48 -0700 Subject: [PATCH 026/388] found and removed another STL-ism --- libraries/ui/src/ui/types/ContextAwareProfile.cpp | 4 ++-- libraries/ui/src/ui/types/ContextAwareProfile.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index 0374de87ff..ef17ad7229 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -38,12 +38,12 @@ RestrictedContextMonitor::TSharedPointer RestrictedContextMonitor::getMonitor(QQ gl_monitorMapProtect.lock(); TMonitorMap::const_iterator lookup = gl_monitorMap.find(context); if (lookup != gl_monitorMap.end()) { - monitor = lookup->second.lock(); + monitor = lookup.value().lock(); assert(monitor); } else if(createIfMissing) { monitor = TSharedPointer::create(context); monitor->_selfPointer = monitor; - gl_monitorMap.insert(TMonitorMap::value_type(context, monitor)); + gl_monitorMap.insert(context, monitor); } gl_monitorMapProtect.unlock(); return monitor; diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h index ec0590eda5..99fee8112d 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.h +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -12,6 +12,7 @@ #define hifi_ContextAwareProfile_h #include +#include #include #include @@ -53,7 +54,7 @@ public: bool _isUninitialized{ true }; private: - typedef std::map TMonitorMap; + typedef QMap TMonitorMap; static QMutex gl_monitorMapProtect; static TMonitorMap gl_monitorMap; From 47c96fcff56218275cf9145fe962140200b14373 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Thu, 28 May 2020 00:03:39 -0700 Subject: [PATCH 027/388] (re-)discovered QMutexLocker, good for tightening up the code a bit --- libraries/ui/src/ui/types/ContextAwareProfile.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index ef17ad7229..2399471e29 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -12,6 +12,7 @@ #include "ContextAwareProfile.h" #include +#include #include #include @@ -24,18 +25,17 @@ QMutex RestrictedContextMonitor::gl_monitorMapProtect; RestrictedContextMonitor::TMonitorMap RestrictedContextMonitor::gl_monitorMap; RestrictedContextMonitor::~RestrictedContextMonitor() { - gl_monitorMapProtect.lock(); + QMutexLocker locker(&gl_monitorMapProtect); TMonitorMap::iterator lookup = gl_monitorMap.find(_context); if (lookup != gl_monitorMap.end()) { gl_monitorMap.erase(lookup); } - gl_monitorMapProtect.unlock(); } RestrictedContextMonitor::TSharedPointer RestrictedContextMonitor::getMonitor(QQmlContext* context, bool createIfMissing) { TSharedPointer monitor; - gl_monitorMapProtect.lock(); + QMutexLocker locker(&gl_monitorMapProtect); TMonitorMap::const_iterator lookup = gl_monitorMap.find(context); if (lookup != gl_monitorMap.end()) { monitor = lookup.value().lock(); @@ -45,7 +45,6 @@ RestrictedContextMonitor::TSharedPointer RestrictedContextMonitor::getMonitor(QQ monitor->_selfPointer = monitor; gl_monitorMap.insert(context, monitor); } - gl_monitorMapProtect.unlock(); return monitor; } From 4f5f46a6237a693e0c7eb81154b58f11f870e159 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Thu, 30 Jul 2020 22:49:54 -0700 Subject: [PATCH 028/388] restructured the code to using a set of ContextAwareProfile objects rather than subscribing to an intermediate object changed _isRestricted from QAtomicInt to std::atomic --- .../ui/src/ui/types/ContextAwareProfile.cpp | 87 ++++++++++--------- .../ui/src/ui/types/ContextAwareProfile.h | 48 +++------- 2 files changed, 59 insertions(+), 76 deletions(-) diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index 2399471e29..5df2d34dfa 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -12,8 +12,9 @@ #include "ContextAwareProfile.h" #include -#include +#include #include +#include #include #include @@ -21,53 +22,59 @@ static const QString RESTRICTED_FLAG_PROPERTY = "RestrictFileAccess"; -QMutex RestrictedContextMonitor::gl_monitorMapProtect; -RestrictedContextMonitor::TMonitorMap RestrictedContextMonitor::gl_monitorMap; - -RestrictedContextMonitor::~RestrictedContextMonitor() { - QMutexLocker locker(&gl_monitorMapProtect); - TMonitorMap::iterator lookup = gl_monitorMap.find(_context); - if (lookup != gl_monitorMap.end()) { - gl_monitorMap.erase(lookup); - } -} - -RestrictedContextMonitor::TSharedPointer RestrictedContextMonitor::getMonitor(QQmlContext* context, bool createIfMissing) { - TSharedPointer monitor; - - QMutexLocker locker(&gl_monitorMapProtect); - TMonitorMap::const_iterator lookup = gl_monitorMap.find(context); - if (lookup != gl_monitorMap.end()) { - monitor = lookup.value().lock(); - assert(monitor); - } else if(createIfMissing) { - monitor = TSharedPointer::create(context); - monitor->_selfPointer = monitor; - gl_monitorMap.insert(context, monitor); - } - return monitor; -} +QReadWriteLock ContextAwareProfile::gl_contextMapProtect; +ContextAwareProfile::ContextMap ContextAwareProfile::gl_contextMap; ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : ContextAwareProfileParent(context), _context(context) { assert(context); - _monitor = RestrictedContextMonitor::getMonitor(context, true); - assert(_monitor); - connect(_monitor.get(), &RestrictedContextMonitor::onIsRestrictedChanged, this, &ContextAwareProfile::onIsRestrictedChanged); - if (_monitor->_isUninitialized) { - _monitor->_isRestricted = isRestrictedGetProperty(); - _monitor->_isUninitialized = false; + { // register our object for future updates + QWriteLocker guard(&gl_contextMapProtect); + ContextMap::iterator setLookup = gl_contextMap.find(_context); + if (setLookup == gl_contextMap.end()) { + setLookup = gl_contextMap.insert(_context, ContextAwareProfileSet()); + } + assert(setLookup != gl_contextMap.end()); + ContextAwareProfileSet& profileSet = setLookup.value(); + assert(profileSet.find(this) == profileSet.end()); + profileSet.insert(this); + } + + _isRestricted.store(isRestrictedGetProperty()); +} + +ContextAwareProfile::~ContextAwareProfile() { + { // deregister our object + QWriteLocker guard(&gl_contextMapProtect); + ContextMap::iterator setLookup = gl_contextMap.find(_context); + assert(setLookup != gl_contextMap.end()); + if (setLookup != gl_contextMap.end()) { + ContextAwareProfileSet& profileSet = setLookup.value(); + assert(profileSet.find(this) != profileSet.end()); + profileSet.remove(this); + if (profileSet.isEmpty()) { + gl_contextMap.erase(setLookup); + } + } } - _isRestricted.store(_monitor->_isRestricted ? 1 : 0); } void ContextAwareProfile::restrictContext(QQmlContext* context, bool restrict) { - RestrictedContextMonitor::TSharedPointer monitor = RestrictedContextMonitor::getMonitor(context, false); + // set the QML property context->setContextProperty(RESTRICTED_FLAG_PROPERTY, restrict); - if (monitor && monitor->_isRestricted != restrict) { - monitor->_isRestricted = restrict; - monitor->onIsRestrictedChanged(restrict); + + // broadcast the new value to any registered ContextAwareProfile objects + { // deregister our object + QReadLocker guard(&gl_contextMapProtect); + ContextMap::const_iterator setLookup = gl_contextMap.find(context); + if (setLookup != gl_contextMap.end()) { + const ContextAwareProfileSet& profileSet = setLookup.value(); + for (ContextAwareProfileSet::const_iterator profileIterator = profileSet.begin(); + profileIterator != profileSet.end(); profileIterator++) { + (*profileIterator)->onIsRestrictedChanged(restrict); + } + } } } @@ -89,9 +96,9 @@ bool ContextAwareProfile::isRestrictedGetProperty() { } void ContextAwareProfile::onIsRestrictedChanged(bool newValue) { - _isRestricted.store(newValue ? 1 : 0); + _isRestricted.store(newValue); } bool ContextAwareProfile::isRestricted() { - return _isRestricted.load() != 0; + return _isRestricted.load(); } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h index 99fee8112d..d8ec762858 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.h +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -11,9 +11,10 @@ #ifndef hifi_ContextAwareProfile_h #define hifi_ContextAwareProfile_h -#include +#include #include -#include +#include +#include #include #if !defined(Q_OS_ANDROID) @@ -29,37 +30,8 @@ using ContextAwareProfileParent = QObject; using RequestInterceptorParent = QObject; #endif -#include - class QQmlContext; -class RestrictedContextMonitor : public QObject { - Q_OBJECT -public: - typedef QSharedPointer TSharedPointer; - typedef QWeakPointer TWeakPointer; - - inline RestrictedContextMonitor(QQmlContext* c) : _context(c) {} - ~RestrictedContextMonitor(); - - static TSharedPointer getMonitor(QQmlContext* context, bool createIfMissing); - -signals: - void onIsRestrictedChanged(bool newValue); - -public: - TWeakPointer _selfPointer; - QQmlContext* _context{ nullptr }; - bool _isRestricted{ true }; - bool _isUninitialized{ true }; - -private: - typedef QMap TMonitorMap; - - static QMutex gl_monitorMapProtect; - static TMonitorMap gl_monitorMap; -}; - class ContextAwareProfile : public ContextAwareProfileParent { Q_OBJECT public: @@ -77,14 +49,18 @@ protected: }; ContextAwareProfile(QQmlContext* parent); - QQmlContext* _context{ nullptr }; - QAtomicInt _isRestricted{ 0 }; - -private slots: + ~ContextAwareProfile(); void onIsRestrictedChanged(bool newValue); + QQmlContext* _context{ nullptr }; + std::atomic _isRestricted{ false }; + private: - RestrictedContextMonitor::TSharedPointer _monitor; + typedef QSet ContextAwareProfileSet; + typedef QMap ContextMap; + + static QReadWriteLock gl_contextMapProtect; + static ContextMap gl_contextMap; }; #endif // hifi_FileTypeProfile_h From 0c7aab1556e1e2a9186787fd251c23245e993a8f Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Sun, 2 Aug 2020 14:38:55 -0700 Subject: [PATCH 029/388] minor code review --- .../ui/src/ui/types/ContextAwareProfile.cpp | 40 +++++++++---------- .../ui/src/ui/types/ContextAwareProfile.h | 15 +++---- 2 files changed, 27 insertions(+), 28 deletions(-) diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index 5df2d34dfa..ee823ec784 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -22,19 +22,19 @@ static const QString RESTRICTED_FLAG_PROPERTY = "RestrictFileAccess"; -QReadWriteLock ContextAwareProfile::gl_contextMapProtect; -ContextAwareProfile::ContextMap ContextAwareProfile::gl_contextMap; +QReadWriteLock ContextAwareProfile::_global_contextMapProtect; +ContextAwareProfile::ContextMap ContextAwareProfile::_global_contextMap; ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : ContextAwareProfileParent(context), _context(context) { assert(context); { // register our object for future updates - QWriteLocker guard(&gl_contextMapProtect); - ContextMap::iterator setLookup = gl_contextMap.find(_context); - if (setLookup == gl_contextMap.end()) { - setLookup = gl_contextMap.insert(_context, ContextAwareProfileSet()); + QWriteLocker guard(&_global_contextMapProtect); + ContextMap::iterator setLookup = _global_contextMap.find(_context); + if (setLookup == _global_contextMap.end()) { + setLookup = _global_contextMap.insert(_context, ContextAwareProfileSet()); } - assert(setLookup != gl_contextMap.end()); + assert(setLookup != _global_contextMap.end()); ContextAwareProfileSet& profileSet = setLookup.value(); assert(profileSet.find(this) == profileSet.end()); profileSet.insert(this); @@ -45,15 +45,15 @@ ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : ContextAwarePro ContextAwareProfile::~ContextAwareProfile() { { // deregister our object - QWriteLocker guard(&gl_contextMapProtect); - ContextMap::iterator setLookup = gl_contextMap.find(_context); - assert(setLookup != gl_contextMap.end()); - if (setLookup != gl_contextMap.end()) { + QWriteLocker guard(&_global_contextMapProtect); + ContextMap::iterator setLookup = _global_contextMap.find(_context); + assert(setLookup != _global_contextMap.end()); + if (setLookup != _global_contextMap.end()) { ContextAwareProfileSet& profileSet = setLookup.value(); assert(profileSet.find(this) != profileSet.end()); profileSet.remove(this); if (profileSet.isEmpty()) { - gl_contextMap.erase(setLookup); + _global_contextMap.erase(setLookup); } } } @@ -65,15 +65,13 @@ void ContextAwareProfile::restrictContext(QQmlContext* context, bool restrict) { context->setContextProperty(RESTRICTED_FLAG_PROPERTY, restrict); // broadcast the new value to any registered ContextAwareProfile objects - { // deregister our object - QReadLocker guard(&gl_contextMapProtect); - ContextMap::const_iterator setLookup = gl_contextMap.find(context); - if (setLookup != gl_contextMap.end()) { - const ContextAwareProfileSet& profileSet = setLookup.value(); - for (ContextAwareProfileSet::const_iterator profileIterator = profileSet.begin(); - profileIterator != profileSet.end(); profileIterator++) { - (*profileIterator)->onIsRestrictedChanged(restrict); - } + QReadLocker guard(&_global_contextMapProtect); + ContextMap::const_iterator setLookup = _global_contextMap.find(context); + if (setLookup != _global_contextMap.end()) { + const ContextAwareProfileSet& profileSet = setLookup.value(); + for (ContextAwareProfileSet::const_iterator profileIterator = profileSet.begin(); + profileIterator != profileSet.end(); profileIterator++) { + (*profileIterator)->onIsRestrictedChanged(restrict); } } } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h index d8ec762858..c6f3020c4f 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.h +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -12,7 +12,7 @@ #define hifi_ContextAwareProfile_h #include -#include +#include #include #include #include @@ -50,17 +50,18 @@ protected: ContextAwareProfile(QQmlContext* parent); ~ContextAwareProfile(); - void onIsRestrictedChanged(bool newValue); + +private: + typedef QSet ContextAwareProfileSet; + typedef QHash ContextMap; QQmlContext* _context{ nullptr }; std::atomic _isRestricted{ false }; -private: - typedef QSet ContextAwareProfileSet; - typedef QMap ContextMap; + static QReadWriteLock _global_contextMapProtect; + static ContextMap _global_contextMap; - static QReadWriteLock gl_contextMapProtect; - static ContextMap gl_contextMap; + void onIsRestrictedChanged(bool newValue); }; #endif // hifi_FileTypeProfile_h From 3783c9897643095c5af7c34b5fab022309911bcd Mon Sep 17 00:00:00 2001 From: Maki Date: Tue, 4 Aug 2020 01:33:19 +0100 Subject: [PATCH 030/388] Allow resolving relative urls when importing entities json --- libraries/octree/src/Octree.cpp | 29 +++++--- libraries/octree/src/Octree.h | 4 +- .../octree/src/OctreeEntitiesFileParser.cpp | 70 ++++++++++++++++++- .../octree/src/OctreeEntitiesFileParser.h | 2 + 4 files changed, 93 insertions(+), 12 deletions(-) diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 897ac142bf..96f2383181 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -685,8 +685,9 @@ bool Octree::readFromFile(const char* fileName) { QDataStream fileInputStream(&file); QFileInfo fileInfo(qFileName); uint64_t fileLength = fileInfo.size(); + QUrl relativeURL = QUrl::fromLocalFile(qFileName).adjusted(QUrl::RemoveFilename); - bool success = readFromStream(fileLength, fileInputStream); + bool success = readFromStream(fileLength, fileInputStream, "", false, relativeURL); file.close(); @@ -708,7 +709,9 @@ bool Octree::readJSONFromGzippedFile(QString qFileName) { } QDataStream jsonStream(jsonData); - return readJSONFromStream(-1, jsonStream); + QUrl relativeURL = QUrl::fromLocalFile(qFileName).adjusted(QUrl::RemoveFilename); + + return readJSONFromStream(-1, jsonStream, "", false, relativeURL); } // hack to get the marketplace id into the entities. We will create a way to get this from a hash of @@ -761,13 +764,15 @@ bool Octree::readFromURL( QByteArray uncompressedJsonData; bool wasCompressed = gunzip(data, uncompressedJsonData); + QUrl relativeURL = QUrl(urlString).adjusted(QUrl::RemoveFilename); + if (wasCompressed) { QDataStream inputStream(uncompressedJsonData); - return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID, isImport, relativeURL); } QDataStream inputStream(data); - return readFromStream(data.size(), inputStream, marketplaceID, isImport); + return readFromStream(data.size(), inputStream, marketplaceID, isImport, relativeURL); } bool Octree::readFromByteArray( @@ -780,20 +785,23 @@ bool Octree::readFromByteArray( QByteArray uncompressedJsonData; bool wasCompressed = gunzip(data, uncompressedJsonData); + QUrl relativeURL = QUrl(urlString).adjusted(QUrl::RemoveFilename); + if (wasCompressed) { QDataStream inputStream(uncompressedJsonData); - return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID); + return readFromStream(uncompressedJsonData.size(), inputStream, marketplaceID, false, relativeURL); } QDataStream inputStream(data); - return readFromStream(data.size(), inputStream, marketplaceID); + return readFromStream(data.size(), inputStream, marketplaceID, false, relativeURL); } bool Octree::readFromStream( uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID, - const bool isImport + const bool isImport, + const QUrl& relativeURL ) { // decide if this is binary SVO or JSON-formatted SVO QIODevice *device = inputStream.device(); @@ -806,7 +814,7 @@ bool Octree::readFromStream( return false; } else { qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength; - return readJSONFromStream(streamLength, inputStream, marketplaceID, isImport); + return readJSONFromStream(streamLength, inputStream, marketplaceID, isImport, relativeURL); } } @@ -837,7 +845,8 @@ bool Octree::readJSONFromStream( uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID, /*=""*/ - const bool isImport + const bool isImport, + const QUrl& relativeURL ) { // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until // we get an eof. Leave streamLength parameter for consistency. @@ -858,7 +867,9 @@ bool Octree::readJSONFromStream( } OctreeEntitiesFileParser octreeParser; + octreeParser.relativeURL = relativeURL; octreeParser.setEntitiesString(jsonBuffer); + QVariantMap asMap; if (!octreeParser.parseEntities(asMap)) { qCritical() << "Couldn't parse Entities JSON:" << octreeParser.getErrorString().c_str(); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index a7885801de..cb46d5151b 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -218,8 +218,8 @@ public: bool readFromFile(const char* filename); bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1, const bool isImport = false); // will support file urls as well... bool readFromByteArray(const QString& url, const QByteArray& byteArray); - bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false); - bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false); + bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false, const QUrl& urlString = QUrl()); + bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false, const QUrl& urlString = QUrl()); bool readJSONFromGzippedFile(QString qFileName); virtual bool readFromMap(QVariantMap& entityDescription, const bool isImport = false) = 0; diff --git a/libraries/octree/src/OctreeEntitiesFileParser.cpp b/libraries/octree/src/OctreeEntitiesFileParser.cpp index e82201adfd..72cfd30ef9 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.cpp +++ b/libraries/octree/src/OctreeEntitiesFileParser.cpp @@ -237,7 +237,75 @@ bool OctreeEntitiesFileParser::readEntitiesArray(QVariantList& entitiesArray) { return false; } - entitiesArray.append(entity.object()); + QJsonObject entityObject = entity.object(); + + // resolve urls starting with ./ or ../ + if (relativeURL.isEmpty() == false) { + bool isDirty = false; + + const QStringList urlKeys { + // model + "modelURL", + "animation.url", + // image + "imageURL", + // web + "sourceUrl", + "scriptURL", + // zone + "ambientLight.ambientURL", + "skybox.url", + // particles + "textures", + // materials + "materialURL", + // ...shared + "href", + "script", + "serverScripts", + "collisionSoundURL", + "compoundShapeURL", + // TODO: deal with materialData and userData + }; + + for (const QString& key : urlKeys) { + if (key.contains('.')) { + // url is inside another object + const QStringList keyPair = key.split('.'); + const QString entityKey = keyPair[0]; + const QString childKey = keyPair[1]; + + if (entityObject.contains(entityKey) && entityObject[entityKey].isObject()) { + QJsonObject childObject = entityObject[entityKey].toObject(); + + if (childObject.contains(childKey) && childObject[childKey].isString()) { + const QString url = childObject[childKey].toString(); + + if (url.startsWith("./") || url.startsWith("../")) { + childObject[childKey] = relativeURL.resolved(url).toString(); + entityObject[entityKey] = childObject; + isDirty = true; + } + } + } + } else { + if (entityObject.contains(key) && entityObject[key].isString()) { + const QString url = entityObject[key].toString(); + + if (url.startsWith("./") || url.startsWith("../")) { + entityObject[key] = relativeURL.resolved(url).toString(); + isDirty = true; + } + } + } + } + + if (isDirty) { + entity.setObject(entityObject); + } + } + + entitiesArray.append(entityObject); _position = matchingBrace; char c = nextToken(); if (c == ']') { diff --git a/libraries/octree/src/OctreeEntitiesFileParser.h b/libraries/octree/src/OctreeEntitiesFileParser.h index bc51896b18..39560710db 100644 --- a/libraries/octree/src/OctreeEntitiesFileParser.h +++ b/libraries/octree/src/OctreeEntitiesFileParser.h @@ -16,12 +16,14 @@ #include #include +#include class OctreeEntitiesFileParser { public: void setEntitiesString(const QByteArray& entitiesContents); bool parseEntities(QVariantMap& parsedEntities); std::string getErrorString() const; + QUrl relativeURL; private: int nextToken(); From 37c613c3d440a7e2a80fa0b4133a35fc89661462 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Mon, 3 Aug 2020 20:50:10 -0700 Subject: [PATCH 031/388] more changes from code review --- .../ui/src/ui/types/ContextAwareProfile.cpp | 47 +++++++++---------- .../ui/src/ui/types/ContextAwareProfile.h | 6 +-- 2 files changed, 23 insertions(+), 30 deletions(-) diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp index ee823ec784..210e1f36b1 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.cpp +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -22,19 +22,19 @@ static const QString RESTRICTED_FLAG_PROPERTY = "RestrictFileAccess"; -QReadWriteLock ContextAwareProfile::_global_contextMapProtect; -ContextAwareProfile::ContextMap ContextAwareProfile::_global_contextMap; +QReadWriteLock ContextAwareProfile::_contextMapProtect; +ContextAwareProfile::ContextMap ContextAwareProfile::_contextMap; ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : ContextAwareProfileParent(context), _context(context) { assert(context); { // register our object for future updates - QWriteLocker guard(&_global_contextMapProtect); - ContextMap::iterator setLookup = _global_contextMap.find(_context); - if (setLookup == _global_contextMap.end()) { - setLookup = _global_contextMap.insert(_context, ContextAwareProfileSet()); + QWriteLocker guard(&_contextMapProtect); + ContextMap::iterator setLookup = _contextMap.find(_context); + if (setLookup == _contextMap.end()) { + setLookup = _contextMap.insert(_context, ContextAwareProfileSet()); } - assert(setLookup != _global_contextMap.end()); + assert(setLookup != _contextMap.end()); ContextAwareProfileSet& profileSet = setLookup.value(); assert(profileSet.find(this) == profileSet.end()); profileSet.insert(this); @@ -44,17 +44,16 @@ ContextAwareProfile::ContextAwareProfile(QQmlContext* context) : ContextAwarePro } ContextAwareProfile::~ContextAwareProfile() { - { // deregister our object - QWriteLocker guard(&_global_contextMapProtect); - ContextMap::iterator setLookup = _global_contextMap.find(_context); - assert(setLookup != _global_contextMap.end()); - if (setLookup != _global_contextMap.end()) { - ContextAwareProfileSet& profileSet = setLookup.value(); - assert(profileSet.find(this) != profileSet.end()); - profileSet.remove(this); - if (profileSet.isEmpty()) { - _global_contextMap.erase(setLookup); - } + // deregister our object + QWriteLocker guard(&_contextMapProtect); + ContextMap::iterator setLookup = _contextMap.find(_context); + assert(setLookup != _contextMap.end()); + if (setLookup != _contextMap.end()) { + ContextAwareProfileSet& profileSet = setLookup.value(); + assert(profileSet.find(this) != profileSet.end()); + profileSet.remove(this); + if (profileSet.isEmpty()) { + _contextMap.erase(setLookup); } } } @@ -65,13 +64,13 @@ void ContextAwareProfile::restrictContext(QQmlContext* context, bool restrict) { context->setContextProperty(RESTRICTED_FLAG_PROPERTY, restrict); // broadcast the new value to any registered ContextAwareProfile objects - QReadLocker guard(&_global_contextMapProtect); - ContextMap::const_iterator setLookup = _global_contextMap.find(context); - if (setLookup != _global_contextMap.end()) { + QReadLocker guard(&_contextMapProtect); + ContextMap::const_iterator setLookup = _contextMap.find(context); + if (setLookup != _contextMap.end()) { const ContextAwareProfileSet& profileSet = setLookup.value(); for (ContextAwareProfileSet::const_iterator profileIterator = profileSet.begin(); profileIterator != profileSet.end(); profileIterator++) { - (*profileIterator)->onIsRestrictedChanged(restrict); + (*profileIterator)->_isRestricted.store(restrict); } } } @@ -93,10 +92,6 @@ bool ContextAwareProfile::isRestrictedGetProperty() { return true; } -void ContextAwareProfile::onIsRestrictedChanged(bool newValue) { - _isRestricted.store(newValue); -} - bool ContextAwareProfile::isRestricted() { return _isRestricted.load(); } diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h index c6f3020c4f..486ac5481a 100644 --- a/libraries/ui/src/ui/types/ContextAwareProfile.h +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -58,10 +58,8 @@ private: QQmlContext* _context{ nullptr }; std::atomic _isRestricted{ false }; - static QReadWriteLock _global_contextMapProtect; - static ContextMap _global_contextMap; - - void onIsRestrictedChanged(bool newValue); + static QReadWriteLock _contextMapProtect; + static ContextMap _contextMap; }; #endif // hifi_FileTypeProfile_h From 87fd039f99632328d4029178e320a4c1eca646f6 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Tue, 4 Aug 2020 08:54:44 -0700 Subject: [PATCH 032/388] fix merge issue --- libraries/render-utils/src/Model.cpp | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 0956392944..92c9b4fafb 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -886,13 +886,6 @@ void Model::updateRenderItemsKey(const render::ScenePointer& scene) { scene->enqueueTransaction(transaction); } -void Model::setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene) { - if (_primitiveMode != primitiveMode) { - _primitiveMode = primitiveMode; - updateRenderItemsKey(scene); - } -} - void Model::setVisibleInScene(bool visible, const render::ScenePointer& scene) { if (Model::isVisible() != visible) { auto keyBuilder = render::ItemKey::Builder(_renderItemKeyGlobalFlags); @@ -965,10 +958,10 @@ void Model::setCauterized(bool cauterized, const render::ScenePointer& scene) { } } -void Model::setPrimitiveMode(PrimitiveMode primitiveMode) { +void Model::setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene) { if (_primitiveMode != primitiveMode) { _primitiveMode = primitiveMode; - setRenderItemsNeedUpdate(); + updateRenderItemsKey(scene); } } From 47e3649298d852f0444af5eaf9abaabba0c1795a Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Thu, 6 Aug 2020 22:28:32 -0400 Subject: [PATCH 033/388] Incremental suffix to duplicated entities This enhancement adds an incremental suffix to the name of the cloned entities resulting from "Duplicate" and "Paste". The goal is to help to figure which entities is the coned one. This addresses issue #575 --- .../entitySelectionTool.js | 56 +++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index 9bab739060..d53349821e 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -6,6 +6,7 @@ // Modified by Daniela Fontes * @DanielaFifo and Tiago Andrade @TagoWill on 4/7/2017 // Modified by David Back on 1/9/2018 // Copyright 2014 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors // // This script implements a class useful for building tools for editing entities. // @@ -19,6 +20,7 @@ const SPACE_LOCAL = "local"; const SPACE_WORLD = "world"; const HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; +const ENTIRE_DOMAIN_SCAN_RADIUS = 27713; Script.include([ "../../libraries/controllers.js", @@ -31,6 +33,55 @@ function deepCopy(v) { return JSON.parse(JSON.stringify(v)); } +function getDuplicateAppendedName(originalName, forcedIncrement) { + var duplicateName, existingNames; + var delayCounter = forcedIncrement; + var duplicateNumber = 1; + var rippedOriginalName = removeNameAppendice(originalName); + + do { + duplicateNumber++; + if (rippedOriginalName === "") { + duplicateName = "(" + duplicateNumber + ")"; + } else { + duplicateName = rippedOriginalName + " (" + duplicateNumber + ")"; + } + existingNames = Entities.findEntitiesByName(duplicateName, Vec3.ZERO, ENTIRE_DOMAIN_SCAN_RADIUS, false); + if (existingNames.length === 0) { + delayCounter--; + } + } while (existingNames.length !== 0 || delayCounter >= 0); + return duplicateName; +} + +function removeNameAppendice(name) { + var appendiceChar = "(0123456789)"; + var rippedName = name; + + for (var i = name.length - 1; i >= 0; i--) { + if (i === (name.length - 1) && name.charAt(i) !== ")") { + rippedName = name; + break; + } else { + if (appendiceChar.indexOf(name.charAt(i)) === -1) { + rippedName = name; + break; + } else { + if (name.charAt(i) == "(" && i === name.length - 2) { + rippedName = name; + break; + } else { + if (name.charAt(i) == "(") { + rippedName = name.substr(0, i-1); + break; + } + } + } + } + } + return rippedName.trim(); +} + SelectionManager = (function() { var that = {}; @@ -289,6 +340,8 @@ SelectionManager = (function() { properties.localRotation = properties.rotation; } + properties.name = getDuplicateAppendedName(properties.name, 0); + properties.localVelocity = Vec3.ZERO; properties.localAngularVelocity = Vec3.ZERO; @@ -477,6 +530,7 @@ SelectionManager = (function() { var copiedProperties = []; var ids = []; + var selectionNo = 0; entityClipboard.entities.forEach(function(originalProperties) { var properties = deepCopy(originalProperties); if (properties.root) { @@ -485,6 +539,8 @@ SelectionManager = (function() { } else { delete properties.position; } + properties.name = getDuplicateAppendedName(properties.name, selectionNo); + selectionNo++; copiedProperties.push(properties); }); From 91ebd378f55f283958d87e9d5bcf276e4d0de08f Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Fri, 7 Aug 2020 12:51:24 -0400 Subject: [PATCH 034/388] Clone suffix without uniqueness check Simpler solution without any hazardous uniqueness check. Cloned entities will systematically get the suffix " (2)", unless their name has already a suffix. In that case, we simply increment the suffix. --- .../entitySelectionTool.js | 52 +++++++------------ 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index d53349821e..cbfaf7f5a6 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -20,7 +20,6 @@ const SPACE_LOCAL = "local"; const SPACE_WORLD = "world"; const HIGHLIGHT_LIST_NAME = "editHandleHighlightList"; -const ENTIRE_DOMAIN_SCAN_RADIUS = 27713; Script.include([ "../../libraries/controllers.js", @@ -33,52 +32,43 @@ function deepCopy(v) { return JSON.parse(JSON.stringify(v)); } -function getDuplicateAppendedName(originalName, forcedIncrement) { - var duplicateName, existingNames; - var delayCounter = forcedIncrement; - var duplicateNumber = 1; - var rippedOriginalName = removeNameAppendice(originalName); - - do { - duplicateNumber++; - if (rippedOriginalName === "") { - duplicateName = "(" + duplicateNumber + ")"; - } else { - duplicateName = rippedOriginalName + " (" + duplicateNumber + ")"; - } - existingNames = Entities.findEntitiesByName(duplicateName, Vec3.ZERO, ENTIRE_DOMAIN_SCAN_RADIUS, false); - if (existingNames.length === 0) { - delayCounter--; - } - } while (existingNames.length !== 0 || delayCounter >= 0); - return duplicateName; -} - -function removeNameAppendice(name) { +function getDuplicateAppendedName(name) { var appendiceChar = "(0123456789)"; + var currentChar = ""; var rippedName = name; - + var existingSequence = 0; + var sequenceReader = ""; for (var i = name.length - 1; i >= 0; i--) { - if (i === (name.length - 1) && name.charAt(i) !== ")") { + currentChar = name.charAt(i); + if (i === (name.length - 1) && currentChar !== ")") { rippedName = name; break; } else { - if (appendiceChar.indexOf(name.charAt(i)) === -1) { + if (appendiceChar.indexOf(currentChar) === -1) { rippedName = name; break; } else { - if (name.charAt(i) == "(" && i === name.length - 2) { + if (currentChar == "(" && i === name.length - 2) { rippedName = name; break; } else { - if (name.charAt(i) == "(") { + if (currentChar == "(") { rippedName = name.substr(0, i-1); + existingSequence = parseInt(sequenceReader); break; + } else { + sequenceReader = currentChar + sequenceReader; } } } } } + if (existingSequence === 0) { + rippedName = rippedName.trim() + " (2)"; + } else { + existingSequence++; + rippedName = rippedName.trim() + " (" + existingSequence + ")"; + } return rippedName.trim(); } @@ -340,7 +330,7 @@ SelectionManager = (function() { properties.localRotation = properties.rotation; } - properties.name = getDuplicateAppendedName(properties.name, 0); + properties.name = getDuplicateAppendedName(properties.name); properties.localVelocity = Vec3.ZERO; properties.localAngularVelocity = Vec3.ZERO; @@ -530,7 +520,6 @@ SelectionManager = (function() { var copiedProperties = []; var ids = []; - var selectionNo = 0; entityClipboard.entities.forEach(function(originalProperties) { var properties = deepCopy(originalProperties); if (properties.root) { @@ -539,8 +528,7 @@ SelectionManager = (function() { } else { delete properties.position; } - properties.name = getDuplicateAppendedName(properties.name, selectionNo); - selectionNo++; + properties.name = getDuplicateAppendedName(properties.name); copiedProperties.push(properties); }); From 856cb5cb2c1d6a6930b236677e2bf633e8d6c381 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Fri, 7 Aug 2020 13:04:01 -0400 Subject: [PATCH 035/388] Replace default URL for Image and Particle Replace default URL for Image and Particle and point now to a local deployed resource. --- scripts/system/create/edit.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index ab21c5776c..d34bd42188 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -44,7 +44,7 @@ var CREATE_TOOLS_WIDTH = 490; var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; var ENTIRE_DOMAIN_SCAN_RADIUS = 27713; -var DEFAULT_IMAGE = "https://hifi-content.s3.amazonaws.com/DomainContent/production/no-image.jpg"; +var DEFAULT_IMAGE = "file:///~/default/default_image.jpg"; var createToolsWindow = new CreateWindow( Script.resolvePath("qml/EditTools.qml"), @@ -436,7 +436,7 @@ const DEFAULT_ENTITY_PROPERTIES = { ParticleEffect: { lifespan: 1.5, maxParticles: 10, - textures: "https://content.highfidelity.com/DomainContent/production/Particles/wispy-smoke.png", + textures: "file:///~/default/default_particle.png", emitRate: 5.5, emitSpeed: 0, speedSpread: 0, From 13365998021f349427082c45228ccc8fdba17b1d Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Fri, 7 Aug 2020 13:09:55 -0400 Subject: [PATCH 036/388] Add default Image and Particle Adding a default folder containing the resource to use by default for Image and Particle entities. I used a new folder because it will be simpler for the copy when the build is generated. (It needs to be recopied in Vircadia/resources) (Instead to do "cherry-picking" to expose specific resources from the images folder.) --- interface/resources/default/default_image.jpg | Bin 0 -> 47282 bytes .../resources/default/default_particle.png | Bin 0 -> 79835 bytes 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 interface/resources/default/default_image.jpg create mode 100644 interface/resources/default/default_particle.png diff --git a/interface/resources/default/default_image.jpg b/interface/resources/default/default_image.jpg new file mode 100644 index 0000000000000000000000000000000000000000..adf61b3e68ddcc2205816ac5290024db2e4b83ff GIT binary patch literal 47282 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 literal 0 HcmV?d00001 diff --git a/interface/resources/default/default_particle.png b/interface/resources/default/default_particle.png new file mode 100644 index 0000000000000000000000000000000000000000..6c781c824befad6e069574c51eecc309f06047d6 GIT binary patch literal 79835 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 i>pI-HapR?2DE|*TvSYzVE)fF&0000 Date: Fri, 7 Aug 2020 13:14:24 -0400 Subject: [PATCH 037/388] Expose the resource "default" folder Expose the resource "default" folder --- interface/CMakeLists.txt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index 608bbb19cc..e5a2e01e33 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -351,6 +351,9 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${PROJECT_SOURCE_DIR}/resources/default" + "${RESOURCES_DEV_DIR}/default" #copy serverless for android COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/serverless" @@ -381,6 +384,9 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" + COMMAND "${CMAKE_COMMAND}" -E copy_directory + "${PROJECT_SOURCE_DIR}/resources/default" + "${RESOURCES_DEV_DIR}/default" COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "${INTERFACE_EXEC_DIR}/scripts" From 1601ea9231d19bc6cffe1b98df124e9c7c861154 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Fri, 7 Aug 2020 15:08:00 -0400 Subject: [PATCH 038/388] Just to prove it works... needs to be done better. --- .../resources/qml/+webengine/BrowserWebView.qml | 3 ++- .../resources/qml/+webengine/QmlWebWindowView.qml | 1 + interface/resources/qml/Web3DSurface.qml | 2 ++ .../controls/+webengine/FlickableWebViewCore.qml | 1 + interface/resources/qml/controls/WebView.qml | 1 + scripts/test.html | 15 +++++++++++++++ 6 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 scripts/test.html diff --git a/interface/resources/qml/+webengine/BrowserWebView.qml b/interface/resources/qml/+webengine/BrowserWebView.qml index fcf4b39a2e..ad837ad043 100644 --- a/interface/resources/qml/+webengine/BrowserWebView.qml +++ b/interface/resources/qml/+webengine/BrowserWebView.qml @@ -8,7 +8,8 @@ WebView { id: webview url: "https://vircadia.com/" profile: FileTypeProfile; - + // backgroundColor: "transparent" + property var parentRoot: null // Create a global EventBridge object for raiseAndLowerKeyboard. diff --git a/interface/resources/qml/+webengine/QmlWebWindowView.qml b/interface/resources/qml/+webengine/QmlWebWindowView.qml index 84ab61ad28..dfabc631ba 100644 --- a/interface/resources/qml/+webengine/QmlWebWindowView.qml +++ b/interface/resources/qml/+webengine/QmlWebWindowView.qml @@ -10,6 +10,7 @@ Controls.WebView { anchors.fill: parent focus: true profile: HFWebEngineProfile; + // backgroundColor: "transparent" property string userScriptUrl: "" diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index d58bcd2eba..39ee85444d 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -3,6 +3,7 @@ // // Created by David Rowe on 16 Dec 2016. // Copyright 2016 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -43,6 +44,7 @@ Item { root.item = newItem root.item.url = url root.item.scriptUrl = scriptUrl + root.item.transparentBackground = true }) } diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index e9546748c0..6fbfcfde1f 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -98,6 +98,7 @@ Item { width: parent.width height: parent.height + backgroundColor: "transparent" profile: HFWebEngineProfile; settings.pluginsEnabled: true diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 24ea11a906..6b57ab85cd 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -42,6 +42,7 @@ Item { id: webroot width: parent.width height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height + // backgroundColor: "transparent" onLoadingChangedCallback: { keyboardRaised = false; diff --git a/scripts/test.html b/scripts/test.html new file mode 100644 index 0000000000..8fb6458eeb --- /dev/null +++ b/scripts/test.html @@ -0,0 +1,15 @@ + + + + + + + +

This is a test. (: (that doesn't work for some reason)

+ + + From c31ccc7a786ee19113d2572ae99cdcbc7f9cfefe Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sat, 8 Aug 2020 00:04:44 -0400 Subject: [PATCH 039/388] Fix unit for localAngularVelocity and other things Revisiting the tooltips for: - colorSpread - localAngularVelocity (Set the correct unit 'deg/s'. This addresses issue #530 ) - damping - angularDamping --- .../system/create/assets/data/createAppTooltips.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 4efd0593fb..c247bc7cae 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -319,7 +319,7 @@ "tooltip": "The finish color of each particle." }, "colorSpread": { - "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." + "tooltip": "The spread in color that each particle is given, resulting in a variety of colors. The variation range (-/+) on each RGB channel to use around the RGB values of the particle color." }, "particleAlphaTriple": { "tooltip": "The opacity of each particle between 0.0 fully transparent and 1.0 completely opaque.", @@ -531,7 +531,7 @@ "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." }, "canCastShadow": { - "tooltip": "If enabled, the geometry of this entity casts shadows when a shadow-casting light source shines on it. Note: Shadows are rendered only on high-profiled computers. This setting will have no effect on computers profiled to medium or low graphics.." + "tooltip": "If enabled, the geometry of this entity casts shadows when a shadow-casting light source shines on it. Note: Shadows are rendered only on high-profiled computers. This setting will have no effect on computers profiled to medium or low graphics." }, "ignorePickIntersection": { "tooltip": "If enabled, this entity will not be considered for ray picks, and will also not occlude other entities when picking." @@ -569,13 +569,13 @@ "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." }, "damping": { - "tooltip": "The linear damping to slow down the linear velocity of an entity over time." + "tooltip": "The linear damping to slow down the linear velocity of an entity over time. A higher damping value slows down the entity more quickly. The default value is for an exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 of its initial value." }, "localAngularVelocity": { - "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." + "tooltip": "The angular velocity of the entity in 'deg/s' with respect to its axes, about its pivot point." }, "angularDamping": { - "tooltip": "The angular damping to slow down the angular velocity of an entity over time." + "tooltip": "The angular damping to slow down the angular velocity of an entity over time. A higher damping value slows down the entity more quickly. The default value is for an exponential decay timescale of 2.0s, where it takes 2.0s for the movement to slow to 1/e = 0.368 of its initial value." }, "restitution": { "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." From 797d18d59dd5b5341c4bb58d89fb90d68df27b47 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sat, 8 Aug 2020 00:07:33 -0400 Subject: [PATCH 040/388] CSS adjustment for the new field type 'vec3rgb' CSS has been adjusted for the new field type 'vec3rgb'. Part of the fix for issue #593. --- scripts/system/html/css/edit-style.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/system/html/css/edit-style.css b/scripts/system/html/css/edit-style.css index c72456d414..ada8116a0d 100644 --- a/scripts/system/html/css/edit-style.css +++ b/scripts/system/html/css/edit-style.css @@ -1688,17 +1688,17 @@ input.rename-entity { margin-left: 4px; margin-right: 10px; } -.fstuple label.red, .fstuple label.x, .fstuple label.w { +.fstuple label.red, .fstuple label.r, .fstuple label.x, .fstuple label.w { color: #C62147; } -.fstuple label.green, .fstuple label.y, .fstuple label.h { +.fstuple label.green, .fstuple label.g, .fstuple label.y, .fstuple label.h { color: #359D85; } -.fstuple label.blue, .fstuple label.z { +.fstuple label.blue, .fstuple label.b, .fstuple label.z { color: #0093C5; } -.xyz.fstuple, .pyr.fstuple { +.xyz.fstuple, .pyr.fstuple, .vec3rgb.fstuple { position: relative; left: -12px; min-width: 50px; From f3a6fa527b2825e3eb8fceaa4c4d607e4608c43d Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sat, 8 Aug 2020 00:14:26 -0400 Subject: [PATCH 041/388] Replace the colorSpread color-picker by a vector Replace the color picker of the attribute particle.colorSpread by a color vec3 To do this, a new type of UI field has been added "vec3rgb" to support the "red, green, blue" vector (non-Color-picker). This addresses issue #593 Fix also the broken particle texture preview that, as for 'atp:/', doesn't support 'file:/'. This will be required for issue #534 --- .../html/js/entityProperties.js | 70 +++++++++++++++++-- 1 file changed, 66 insertions(+), 4 deletions(-) diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index 182dddf817..f8f7063828 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -1133,7 +1133,13 @@ const GROUPS = [ }, { label: "Color Spread", - type: "color", + type: "vec3rgb", + vec3Type: "vec3rgb", + min: 0, + max: 255, + step: 1, + decimals: 0, + subLabels: [ "r", "g", "b" ], propertyID: "colorSpread", }, { @@ -1791,6 +1797,8 @@ function getPropertyInputElement(propertyID) { return { x: property.elNumberX.elInput, y: property.elNumberY.elInput, z: property.elNumberZ.elInput }; case 'color': return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput }; + case 'vec3rgb': + return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput }; case 'icon': return property.elLabel; case 'dynamic-multiselect': @@ -1889,6 +1897,12 @@ function resetProperties() { property.elNumberB.setValue("", false); break; } + case 'vec3rgb': { + property.elNumberR.setValue("", false); + property.elNumberG.setValue("", false); + property.elNumberB.setValue("", false); + break; + } case 'dropdown': { property.elInput.classList.remove('multi-diff'); property.elInput.value = ""; @@ -1995,7 +2009,7 @@ function isCurrentlyDraggingProperty(propertyName) { return properties[propertyName] && properties[propertyName].dragging === true; } -const SUPPORTED_FALLBACK_TYPES = ['number', 'number-draggable', 'rect', 'vec3', 'vec2', 'color']; +const SUPPORTED_FALLBACK_TYPES = ['number', 'number-draggable', 'rect', 'vec3', 'vec2', 'color', 'vec3rgb']; function getMultiplePropertyValue(originalPropertyName) { // if this is a compound property name (i.e. animation.running) @@ -2051,6 +2065,9 @@ function getMultiplePropertyValue(originalPropertyName) { case 'color': isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; break; + case 'vec3rgb': + isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; + break; } if (isPropertyNotNumber) { if (fallbackMultiValue === null) { @@ -2662,6 +2679,33 @@ function createVec3Property(property, elProperty) { return elResult; } +function createVec3rgbProperty(property, elProperty) { + let propertyData = property.data; + + elProperty.className = propertyData.vec3Type + " fstuple"; + + let elNumberR = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); + let elNumberG = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); + let elNumberB = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]); + elProperty.appendChild(elNumberR.elDiv); + elProperty.appendChild(elNumberG.elDiv); + elProperty.appendChild(elNumberB.elDiv); + + elNumberR.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'red')); + elNumberG.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'green')); + elNumberB.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'blue')); + + elNumberR.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'red')); + elNumberG.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'green')); + elNumberB.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'blue')); + + let elResult = []; + elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberR; + elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberG; + elResult[VECTOR_ELEMENTS.Z_NUMBER] = elNumberB; + return elResult; +} + function createVec2Property(property, elProperty) { let propertyData = property.data; @@ -2856,7 +2900,7 @@ function createTextureProperty(property, elProperty) { let imageLoad = function(url) { elDiv.style.display = null; - if (url.slice(0, 5).toLowerCase() === "atp:/") { + if (url.slice(0, 5).toLowerCase() === "atp:/" || url.slice(0, 6).toLowerCase() === "file:/") { elImage.src = ""; elImage.style.display = "none"; elDiv.classList.remove("with-texture"); @@ -3048,6 +3092,13 @@ function createProperty(propertyData, propertyElementID, propertyName, propertyI property.elNumberB = elColor[COLOR_ELEMENTS.BLUE_NUMBER]; break; } + case 'vec3rgb': { + let elVec3 = createVec3rgbProperty(property, elProperty); + property.elNumberR = elVec3[VECTOR_ELEMENTS.X_NUMBER]; + property.elNumberG = elVec3[VECTOR_ELEMENTS.Y_NUMBER]; + property.elNumberB = elVec3[VECTOR_ELEMENTS.Z_NUMBER]; + break; + } case 'dropdown': { property.elInput = createDropdownProperty(property, propertyID, elProperty); break; @@ -4096,6 +4147,13 @@ function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { property.elNumberB.setValue(displayColor.blue); break; } + case 'vec3rgb': { + let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); + property.elNumberR.setValue(detailedNumberDiff.averagePerPropertyComponent.red, detailedNumberDiff.propertyComponentDiff.red); + property.elNumberG.setValue(detailedNumberDiff.averagePerPropertyComponent.green, detailedNumberDiff.propertyComponentDiff.green); + property.elNumberB.setValue(detailedNumberDiff.averagePerPropertyComponent.blue, detailedNumberDiff.propertyComponentDiff.blue); + break; + } case 'dropdown': { property.elInput.classList.toggle('multi-diff', isMultiDiffValue); property.elInput.value = isMultiDiffValue ? "" : propertyValue; @@ -4350,7 +4408,8 @@ function loaded() { properties[propertyID] = property; } if (propertyData.type === 'number' || propertyData.type === 'number-draggable' || - propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') { + propertyData.type === 'vec2' || propertyData.type === 'vec3' || + propertyData.type === 'rect' || propertyData.type === 'vec3rgb') { propertyRangeRequests.push(propertyID); } @@ -4435,6 +4494,9 @@ function loaded() { case 'vec2': updateVectorMinMax(properties[property]); break; + case 'vec3rgb': + updateVectorMinMax(properties[property]); + break; case 'rect': updateRectMinMax(properties[property]); break; From d31f161d887aaf973c3854f4ea6fbc302d4e2cdd Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sun, 9 Aug 2020 00:10:40 -0400 Subject: [PATCH 042/388] Remove the deprecated "acceleration" attribute This removes the "acceleration" attribute from the Create App. (Physic section) because this attribute is deprecated. --- .../entityProperties/html/js/entityProperties.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index f8f7063828..0b0a3a286a 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -1657,16 +1657,6 @@ const GROUPS = [ decimals: 4, unit: "m/s2", propertyID: "gravity", - }, - { - label: "Acceleration", - type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - step: 0.1, - decimals: 4, - unit: "m/s2", - propertyID: "acceleration", } ] }, From 440db34a799cbe40ae3fdc9afdfa54cb38502d63 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sun, 9 Aug 2020 15:21:34 -0400 Subject: [PATCH 043/388] Change the image for a screen calibration picture Change the default image for a screen calibration picture --- interface/resources/default/default_image.jpg | Bin 47282 -> 156659 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/interface/resources/default/default_image.jpg b/interface/resources/default/default_image.jpg index adf61b3e68ddcc2205816ac5290024db2e4b83ff..830af928baa56f616a0ff2d10adfff9a3712528f 100644 GIT binary patch literal 156659 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 +qUb_6bSDyDtYvvXr%_;>@j3uU2X&syfp< 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 z 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 G&;JkUbGWwv literal 47282 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 From 2e5244663ebf7e1e1cb1ededd2f89d557cd81f06 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Sun, 9 Aug 2020 14:57:00 -0700 Subject: [PATCH 044/388] transition listener registration from member string name to member string pointer --- assignment-client/src/Agent.cpp | 7 +- assignment-client/src/AssignmentClient.cpp | 6 +- .../src/AssignmentClientMonitor.cpp | 3 +- assignment-client/src/assets/AssetServer.cpp | 15 +- assignment-client/src/audio/AudioMixer.cpp | 17 +- assignment-client/src/avatars/AvatarMixer.cpp | 40 ++-- .../src/entities/EntityServer.cpp | 3 +- .../src/messages/MessagesMixer.cpp | 9 +- assignment-client/src/octree/OctreeServer.cpp | 6 +- .../src/scripts/EntityScriptServer.cpp | 17 +- domain-server/src/DomainGatekeeper.cpp | 3 +- domain-server/src/DomainServer.cpp | 57 ++++-- .../src/DomainServerSettingsManager.h | 2 + interface/src/commerce/Wallet.cpp | 6 +- .../src/octree/OctreePacketProcessor.cpp | 3 +- .../ScreenshareScriptingInterface.cpp | 3 +- interface/src/ui/DomainConnectionModel.cpp | 3 +- .../ui/overlays/ContextOverlayInterface.cpp | 3 +- libraries/audio-client/src/AudioClient.cpp | 21 +- libraries/avatars/src/AvatarHashMap.cpp | 12 +- libraries/avatars/src/ClientTraitsHandler.cpp | 3 +- .../entities/src/EntityEditPacketSender.cpp | 3 +- .../src/EntityScriptServerLogClient.cpp | 3 +- .../entities/src/EntityScriptingInterface.cpp | 3 +- libraries/networking/src/AssetClient.cpp | 12 +- .../networking/src/EntityScriptClient.cpp | 3 +- libraries/networking/src/MessagesClient.cpp | 3 +- libraries/networking/src/NodeList.cpp | 42 ++-- libraries/networking/src/PacketReceiver.cpp | 180 +++++------------- libraries/networking/src/PacketReceiver.h | 98 +++++++++- libraries/networking/src/ResourceCache.cpp | 5 +- libraries/octree/src/OctreePersistThread.cpp | 3 +- 32 files changed, 343 insertions(+), 251 deletions(-) diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 59e53bb2cb..a8ce7e30e5 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -112,11 +112,12 @@ Agent::Agent(ReceivedMessage& message) : packetReceiver.registerListenerForTypes( { PacketType::MixedAudio, PacketType::SilentAudioFrame }, - this, "handleAudioPacket"); + PacketReceiver::makeUnsourcedListenerReference(this, &Agent::handleAudioPacket)); packetReceiver.registerListenerForTypes( { PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, - this, "handleOctreePacket"); - packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); + PacketReceiver::makeSourcedListenerReference(this, &Agent::handleOctreePacket)); + packetReceiver.registerListener(PacketType::SelectedAudioFormat, + PacketReceiver::makeUnsourcedListenerReference(this, &Agent::handleSelectedAudioFormat)); // 100Hz timer for audio const int TARGET_INTERVAL_MSEC = 10; // 10ms diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index ce724d7368..50eee258ab 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -118,8 +118,10 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri setUpStatusToMonitor(); } auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::CreateAssignment, this, "handleCreateAssignmentPacket"); - packetReceiver.registerListener(PacketType::StopNode, this, "handleStopNodePacket"); + packetReceiver.registerListener(PacketType::CreateAssignment, + PacketReceiver::makeUnsourcedListenerReference(this, &AssignmentClient::handleCreateAssignmentPacket)); + packetReceiver.registerListener(PacketType::StopNode, + PacketReceiver::makeUnsourcedListenerReference(this, &AssignmentClient::handleStopNodePacket)); } void AssignmentClient::stopAssignmentClient() { diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 4c7f71a7aa..68c0dfc9fd 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -72,7 +72,8 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen auto nodeList = DependencyManager::set(listenPort); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::AssignmentClientStatus, this, "handleChildStatusPacket"); + packetReceiver.registerListener(PacketType::AssignmentClientStatus, + PacketReceiver::makeUnsourcedListenerReference(this, &AssignmentClientMonitor::handleChildStatusPacket)); adjustOSResources(std::max(_numAssignmentClientForks, _maxAssignmentClientForks)); // use QProcess to fork off a process for each of the child assignment clients diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index b3344e3832..ffb6747fd7 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -308,7 +308,8 @@ AssetServer::AssetServer(ReceivedMessage& message) : // Queue all requests until the Asset Server is fully setup auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListenerForTypes({ PacketType::AssetGet, PacketType::AssetGetInfo, PacketType::AssetUpload, PacketType::AssetMappingOperation }, this, "queueRequests"); + packetReceiver.registerListenerForTypes({ PacketType::AssetGet, PacketType::AssetGetInfo, PacketType::AssetUpload, PacketType::AssetMappingOperation }, + PacketReceiver::makeSourcedListenerReference(this, &AssetServer::queueRequests)); #ifdef Q_OS_WIN updateConsumedCores(); @@ -464,10 +465,14 @@ void AssetServer::completeSetup() { qCDebug(asset_server) << "Overriding temporary queuing packet handler."; // We're fully setup, override the request queueing handler and replay all requests auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::AssetGet, this, "handleAssetGet"); - packetReceiver.registerListener(PacketType::AssetGetInfo, this, "handleAssetGetInfo"); - packetReceiver.registerListener(PacketType::AssetUpload, this, "handleAssetUpload"); - packetReceiver.registerListener(PacketType::AssetMappingOperation, this, "handleAssetMappingOperation"); + packetReceiver.registerListener(PacketType::AssetGet, + PacketReceiver::makeSourcedListenerReference(this, &AssetServer::handleAssetGet)); + packetReceiver.registerListener(PacketType::AssetGetInfo, + PacketReceiver::makeSourcedListenerReference(this, &AssetServer::handleAssetGetInfo)); + packetReceiver.registerListener(PacketType::AssetUpload, + PacketReceiver::makeSourcedListenerReference(this, &AssetServer::handleAssetUpload)); + packetReceiver.registerListener(PacketType::AssetMappingOperation, + PacketReceiver::makeSourcedListenerReference(this, &AssetServer::handleAssetMappingOperation)); replayRequests(); } diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index 161a6f4285..42a269c544 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -101,20 +101,23 @@ AudioMixer::AudioMixer(ReceivedMessage& message) : PacketType::InjectorGainSet, PacketType::AudioSoloRequest, PacketType::StopInjector }, - this, "queueAudioPacket"); + PacketReceiver::makeSourcedListenerReference(this, &AudioMixer::queueAudioPacket) + ); // packets whose consequences are global should be processed on the main thread - packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); - packetReceiver.registerListener(PacketType::NodeMuteRequest, this, "handleNodeMuteRequestPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); + packetReceiver.registerListener(PacketType::MuteEnvironment, + PacketReceiver::makeSourcedListenerReference(this, &AudioMixer::handleMuteEnvironmentPacket)); + packetReceiver.registerListener(PacketType::NodeMuteRequest, + PacketReceiver::makeSourcedListenerReference(this, &AudioMixer::handleNodeMuteRequestPacket)); + packetReceiver.registerListener(PacketType::KillAvatar, + PacketReceiver::makeSourcedListenerReference(this, &AudioMixer::handleKillAvatarPacket)); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedMicrophoneAudioNoEcho, PacketType::ReplicatedMicrophoneAudioWithEcho, PacketType::ReplicatedInjectAudio, - PacketType::ReplicatedSilentAudioFrame - }, - this, "queueReplicatedAudioPacket" + PacketType::ReplicatedSilentAudioFrame }, + PacketReceiver::makeUnsourcedListenerReference(this, &AudioMixer::queueReplicatedAudioPacket) ); connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled); diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 807f54953e..27b7d0d302 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -71,26 +71,38 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::AvatarData, this, "queueIncomingPacket"); - packetReceiver.registerListener(PacketType::AdjustAvatarSorting, this, "handleAdjustAvatarSorting"); - packetReceiver.registerListener(PacketType::AvatarQuery, this, "handleAvatarQueryPacket"); - packetReceiver.registerListener(PacketType::AvatarIdentity, this, "handleAvatarIdentityPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket"); - packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket"); - packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, this, "handleRadiusIgnoreRequestPacket"); - packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); - packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); - packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); + packetReceiver.registerListener(PacketType::AvatarData, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::queueIncomingPacket)); + packetReceiver.registerListener(PacketType::AdjustAvatarSorting, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleAdjustAvatarSorting)); + packetReceiver.registerListener(PacketType::AvatarQuery, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleAvatarQueryPacket)); + packetReceiver.registerListener(PacketType::AvatarIdentity, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleAvatarIdentityPacket)); + packetReceiver.registerListener(PacketType::KillAvatar, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleKillAvatarPacket)); + packetReceiver.registerListener(PacketType::NodeIgnoreRequest, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleNodeIgnoreRequestPacket)); + packetReceiver.registerListener(PacketType::RadiusIgnoreRequest, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleRadiusIgnoreRequestPacket)); + packetReceiver.registerListener(PacketType::RequestsDomainListData, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleRequestsDomainListDataPacket)); + packetReceiver.registerListener(PacketType::SetAvatarTraits, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::queueIncomingPacket)); + packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::queueIncomingPacket)); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, - this, "handleOctreePacket"); - packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket"); + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::handleOctreePacket)); + packetReceiver.registerListener(PacketType::ChallengeOwnership, + PacketReceiver::makeSourcedListenerReference(this, &AvatarMixer::queueIncomingPacket)); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, PacketType::ReplicatedKillAvatar - }, this, "handleReplicatedPacket"); + }, PacketReceiver::makeUnsourcedListenerReference(this, &AvatarMixer::handleReplicatedPacket)); - packetReceiver.registerListener(PacketType::ReplicatedBulkAvatarData, this, "handleReplicatedBulkAvatarPacket"); + packetReceiver.registerListener(PacketType::ReplicatedBulkAvatarData, + PacketReceiver::makeUnsourcedListenerReference(this, &AvatarMixer::handleReplicatedBulkAvatarPacket)); auto nodeList = DependencyManager::get(); connect(nodeList.data(), &NodeList::packetVersionMismatch, this, &AvatarMixer::handlePacketVersionMismatch); diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 4c4fcbf2dd..e68f95bda0 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -59,8 +59,7 @@ EntityServer::EntityServer(ReceivedMessage& message) : PacketType::ChallengeOwnership, PacketType::ChallengeOwnershipRequest, PacketType::ChallengeOwnershipReply }, - this, - "handleEntityPacket"); + PacketReceiver::makeSourcedListenerReference(this, &EntityServer::handleEntityPacket)); connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); _dynamicDomainVerificationTimer.setSingleShot(true); diff --git a/assignment-client/src/messages/MessagesMixer.cpp b/assignment-client/src/messages/MessagesMixer.cpp index d2127835f9..bcf4881fcf 100644 --- a/assignment-client/src/messages/MessagesMixer.cpp +++ b/assignment-client/src/messages/MessagesMixer.cpp @@ -25,9 +25,12 @@ MessagesMixer::MessagesMixer(ReceivedMessage& message) : ThreadedAssignment(mess { connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &MessagesMixer::nodeKilled); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessages"); - packetReceiver.registerListener(PacketType::MessagesSubscribe, this, "handleMessagesSubscribe"); - packetReceiver.registerListener(PacketType::MessagesUnsubscribe, this, "handleMessagesUnsubscribe"); + packetReceiver.registerListener(PacketType::MessagesData, + PacketReceiver::makeSourcedListenerReference(this, &MessagesMixer::handleMessages)); + packetReceiver.registerListener(PacketType::MessagesSubscribe, + PacketReceiver::makeSourcedListenerReference(this, &MessagesMixer::handleMessagesSubscribe)); + packetReceiver.registerListener(PacketType::MessagesUnsubscribe, + PacketReceiver::makeSourcedListenerReference(this, &MessagesMixer::handleMessagesUnsubscribe)); } void MessagesMixer::nodeKilled(SharedNodePointer killedNode) { diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index 63520262cd..f72ab0ac05 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1122,8 +1122,10 @@ void OctreeServer::run() { void OctreeServer::domainSettingsRequestComplete() { auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket"); - packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket"); + packetReceiver.registerListener(PacketType::OctreeDataNack, + PacketReceiver::makeSourcedListenerReference(this, &OctreeServer::handleOctreeDataNackPacket)); + packetReceiver.registerListener(getMyQueryMessageType(), + PacketReceiver::makeSourcedListenerReference(this, &OctreeServer::handleOctreeQueryPacket)); qDebug(octree_server) << "Received domain settings"; diff --git a/assignment-client/src/scripts/EntityScriptServer.cpp b/assignment-client/src/scripts/EntityScriptServer.cpp index 7c3d491470..065ab12abc 100644 --- a/assignment-client/src/scripts/EntityScriptServer.cpp +++ b/assignment-client/src/scripts/EntityScriptServer.cpp @@ -83,13 +83,18 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, - this, "handleOctreePacket"); - packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptServer::handleOctreePacket)); + packetReceiver.registerListener(PacketType::SelectedAudioFormat, + PacketReceiver::makeUnsourcedListenerReference(this, &EntityScriptServer::handleSelectedAudioFormat)); - packetReceiver.registerListener(PacketType::ReloadEntityServerScript, this, "handleReloadEntityServerScriptPacket"); - packetReceiver.registerListener(PacketType::EntityScriptGetStatus, this, "handleEntityScriptGetStatusPacket"); - packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket"); - packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket"); + packetReceiver.registerListener(PacketType::ReloadEntityServerScript, + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptServer::handleReloadEntityServerScriptPacket)); + packetReceiver.registerListener(PacketType::EntityScriptGetStatus, + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptServer::handleEntityScriptGetStatusPacket)); + packetReceiver.registerListener(PacketType::EntityServerScriptLog, + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptServer::handleEntityServerScriptLogPacket)); + packetReceiver.registerListener(PacketType::EntityScriptCallMethod, + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptServer::handleEntityScriptCallMethodPacket)); static const int LOG_INTERVAL = MSECS_PER_SECOND / 10; auto timer = new QTimer(this); diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index ead4002334..ef37b80d1b 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -17,7 +17,8 @@ #include #include -#include +#include +#include #include #include diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 41b0e98ec5..455a425330 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -765,32 +765,51 @@ void DomainServer::setupNodeListAndAssignments() { // register as the packet receiver for the types we want PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::RequestAssignment, this, "processRequestAssignmentPacket"); - packetReceiver.registerListener(PacketType::DomainListRequest, this, "processListRequestPacket"); - packetReceiver.registerListener(PacketType::DomainServerPathQuery, this, "processPathQueryPacket"); - packetReceiver.registerListener(PacketType::NodeJsonStats, this, "processNodeJSONStatsPacket"); - packetReceiver.registerListener(PacketType::DomainDisconnectRequest, this, "processNodeDisconnectRequestPacket"); - packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacket"); + packetReceiver.registerListener(PacketType::RequestAssignment, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processRequestAssignmentPacket)); + packetReceiver.registerListener(PacketType::DomainListRequest, + PacketReceiver::makeSourcedListenerReference(this, &DomainServer::processListRequestPacket)); + packetReceiver.registerListener(PacketType::DomainServerPathQuery, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processPathQueryPacket)); + packetReceiver.registerListener(PacketType::NodeJsonStats, + PacketReceiver::makeSourcedListenerReference(this, &DomainServer::processNodeJSONStatsPacket)); + packetReceiver.registerListener(PacketType::DomainDisconnectRequest, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processNodeDisconnectRequestPacket)); + packetReceiver.registerListener(PacketType::AvatarZonePresence, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processAvatarZonePresencePacket)); // NodeList won't be available to the settings manager when it is created, so call registerListener here - packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket"); - packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket"); - packetReceiver.registerListener(PacketType::UsernameFromIDRequest, &_settingsManager, "processUsernameFromIDRequestPacket"); + packetReceiver.registerListener(PacketType::DomainSettingsRequest, + PacketReceiver::makeUnsourcedListenerReference(&_settingsManager, &DomainServerSettingsManager::processSettingsRequestPacket)); + packetReceiver.registerListener(PacketType::NodeKickRequest, + PacketReceiver::makeSourcedListenerReference(&_settingsManager, &DomainServerSettingsManager::processNodeKickRequestPacket)); + packetReceiver.registerListener(PacketType::UsernameFromIDRequest, + PacketReceiver::makeSourcedListenerReference(&_settingsManager, &DomainServerSettingsManager::processUsernameFromIDRequestPacket)); // register the gatekeeper for the packets it needs to receive - packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket"); - packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket"); - packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket"); - packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket"); + packetReceiver.registerListener(PacketType::DomainConnectRequest, + PacketReceiver::makeUnsourcedListenerReference(&_gatekeeper, &DomainGatekeeper::processConnectRequestPacket)); + packetReceiver.registerListener(PacketType::ICEPing, + PacketReceiver::makeUnsourcedListenerReference(&_gatekeeper, &DomainGatekeeper::processICEPingPacket)); + packetReceiver.registerListener(PacketType::ICEPingReply, + PacketReceiver::makeUnsourcedListenerReference(&_gatekeeper, &DomainGatekeeper::processICEPingReplyPacket)); + packetReceiver.registerListener(PacketType::ICEServerPeerInformation, + PacketReceiver::makeUnsourcedListenerReference(&_gatekeeper, &DomainGatekeeper::processICEPeerInformationPacket)); - packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket"); - packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, this, "processICEServerHeartbeatACK"); + packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processICEServerHeartbeatDenialPacket)); + packetReceiver.registerListener(PacketType::ICEServerHeartbeatACK, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processICEServerHeartbeatACK)); - packetReceiver.registerListener(PacketType::OctreeDataFileRequest, this, "processOctreeDataRequestMessage"); - packetReceiver.registerListener(PacketType::OctreeDataPersist, this, "processOctreeDataPersistMessage"); + packetReceiver.registerListener(PacketType::OctreeDataFileRequest, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processOctreeDataRequestMessage)); + packetReceiver.registerListener(PacketType::OctreeDataPersist, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::processOctreeDataPersistMessage)); - packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacementRequest"); - packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl, this, "handleDomainContentReplacementFromURLRequest"); + packetReceiver.registerListener(PacketType::OctreeFileReplacement, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::handleOctreeFileReplacementRequest)); + packetReceiver.registerListener(PacketType::DomainContentReplacementFromUrl, + PacketReceiver::makeUnsourcedListenerReference(this, &DomainServer::handleDomainContentReplacementFromURLRequest)); // set a custom packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::isPacketVerified); diff --git a/domain-server/src/DomainServerSettingsManager.h b/domain-server/src/DomainServerSettingsManager.h index 294e441ba4..d0c7812a99 100644 --- a/domain-server/src/DomainServerSettingsManager.h +++ b/domain-server/src/DomainServerSettingsManager.h @@ -211,6 +211,8 @@ private: /// guard read/write access from multiple threads to settings QReadWriteLock _settingsLock { QReadWriteLock::Recursive }; + + friend class DomainServer; }; #endif // hifi_DomainServerSettingsManager_h diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c449874117..95533b0fb9 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -288,8 +288,10 @@ Wallet::Wallet() { auto& packetReceiver = nodeList->getPacketReceiver(); _passphrase = new QString(""); - packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); - packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); + packetReceiver.registerListener(PacketType::ChallengeOwnership, + PacketReceiver::makeSourcedListenerReference(this, &Wallet::handleChallengeOwnershipPacket)); + packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, + PacketReceiver::makeSourcedListenerReference(this, &Wallet::handleChallengeOwnershipPacket)); connect(ledger.data(), &Ledger::accountResult, this, [](QJsonObject result) { auto wallet = DependencyManager::get(); diff --git a/interface/src/octree/OctreePacketProcessor.cpp b/interface/src/octree/OctreePacketProcessor.cpp index bc3c1afdd5..c2823e364f 100644 --- a/interface/src/octree/OctreePacketProcessor.cpp +++ b/interface/src/octree/OctreePacketProcessor.cpp @@ -25,7 +25,8 @@ OctreePacketProcessor::OctreePacketProcessor(): auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); const PacketReceiver::PacketTypeList octreePackets = { PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase, PacketType::EntityQueryInitialResultsComplete }; - packetReceiver.registerDirectListenerForTypes(octreePackets, this, "handleOctreePacket"); + packetReceiver.registerDirectListenerForTypes(octreePackets, + PacketReceiver::makeSourcedListenerReference(this, &OctreePacketProcessor::handleOctreePacket)); } OctreePacketProcessor::~OctreePacketProcessor() { } diff --git a/interface/src/scripting/ScreenshareScriptingInterface.cpp b/interface/src/scripting/ScreenshareScriptingInterface.cpp index 3bf8336fe4..54f3e195ee 100644 --- a/interface/src/scripting/ScreenshareScriptingInterface.cpp +++ b/interface/src/scripting/ScreenshareScriptingInterface.cpp @@ -42,7 +42,8 @@ ScreenshareScriptingInterface::ScreenshareScriptingInterface() { // This packet listener handles the packet containing information about the latest zone ID in which we are allowed to share. auto nodeList = DependencyManager::get(); PacketReceiver& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::AvatarZonePresence, this, "processAvatarZonePresencePacketOnClient"); + packetReceiver.registerListener(PacketType::AvatarZonePresence, + PacketReceiver::makeUnsourcedListenerReference(this, &ScreenshareScriptingInterface::processAvatarZonePresencePacketOnClient)); }; ScreenshareScriptingInterface::~ScreenshareScriptingInterface() { diff --git a/interface/src/ui/DomainConnectionModel.cpp b/interface/src/ui/DomainConnectionModel.cpp index 83aa18420c..596662a664 100644 --- a/interface/src/ui/DomainConnectionModel.cpp +++ b/interface/src/ui/DomainConnectionModel.cpp @@ -9,7 +9,8 @@ // #include "DomainConnectionModel.h" -#include +#include +#include #include #include diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 0f1c8978f0..807eed89ba 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -81,7 +81,8 @@ ContextOverlayInterface::ContextOverlayInterface() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket"); + packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, + PacketReceiver::makeSourcedListenerReference(this, &ContextOverlayInterface::handleChallengeOwnershipReplyPacket)); _challengeOwnershipTimeoutTimer.setSingleShot(true); } diff --git a/libraries/audio-client/src/AudioClient.cpp b/libraries/audio-client/src/AudioClient.cpp index 4e8c88560b..5a255ffb82 100644 --- a/libraries/audio-client/src/AudioClient.cpp +++ b/libraries/audio-client/src/AudioClient.cpp @@ -369,13 +369,20 @@ AudioClient::AudioClient() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket"); - packetReceiver.registerListener(PacketType::AudioEnvironment, this, "handleAudioEnvironmentDataPacket"); - packetReceiver.registerListener(PacketType::SilentAudioFrame, this, "handleAudioDataPacket"); - packetReceiver.registerListener(PacketType::MixedAudio, this, "handleAudioDataPacket"); - packetReceiver.registerListener(PacketType::NoisyMute, this, "handleNoisyMutePacket"); - packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket"); - packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat"); + packetReceiver.registerListener(PacketType::AudioStreamStats, + PacketReceiver::makeSourcedListenerReference(&_stats, &AudioIOStats::processStreamStatsPacket)); + packetReceiver.registerListener(PacketType::AudioEnvironment, + PacketReceiver::makeUnsourcedListenerReference(this, &AudioClient::handleAudioEnvironmentDataPacket)); + packetReceiver.registerListener(PacketType::SilentAudioFrame, + PacketReceiver::makeUnsourcedListenerReference(this, &AudioClient::handleAudioDataPacket)); + packetReceiver.registerListener(PacketType::MixedAudio, + PacketReceiver::makeUnsourcedListenerReference(this, &AudioClient::handleAudioDataPacket)); + packetReceiver.registerListener(PacketType::NoisyMute, + PacketReceiver::makeUnsourcedListenerReference(this, &AudioClient::handleNoisyMutePacket)); + packetReceiver.registerListener(PacketType::MuteEnvironment, + PacketReceiver::makeUnsourcedListenerReference(this, &AudioClient::handleMuteEnvironmentPacket)); + packetReceiver.registerListener(PacketType::SelectedAudioFormat, + PacketReceiver::makeUnsourcedListenerReference(this, &AudioClient::handleSelectedAudioFormat)); auto& domainHandler = nodeList->getDomainHandler(); connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, [this] { diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index d0b315b524..7ed93f346d 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -123,10 +123,14 @@ AvatarHashMap::AvatarHashMap() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::BulkAvatarData, this, "processAvatarDataPacket"); - packetReceiver.registerListener(PacketType::KillAvatar, this, "processKillAvatar"); - packetReceiver.registerListener(PacketType::AvatarIdentity, this, "processAvatarIdentityPacket"); - packetReceiver.registerListener(PacketType::BulkAvatarTraits, this, "processBulkAvatarTraits"); + packetReceiver.registerListener(PacketType::BulkAvatarData, + PacketReceiver::makeSourcedListenerReference(this, &AvatarHashMap::processAvatarDataPacket)); + packetReceiver.registerListener(PacketType::KillAvatar, + PacketReceiver::makeSourcedListenerReference(this, &AvatarHashMap::processKillAvatar)); + packetReceiver.registerListener(PacketType::AvatarIdentity, + PacketReceiver::makeSourcedListenerReference(this, &AvatarHashMap::processAvatarIdentityPacket)); + packetReceiver.registerListener(PacketType::BulkAvatarTraits, + PacketReceiver::makeSourcedListenerReference(this, &AvatarHashMap::processBulkAvatarTraits)); connect(nodeList.data(), &NodeList::uuidChanged, this, &AvatarHashMap::sessionUUIDChanged); diff --git a/libraries/avatars/src/ClientTraitsHandler.cpp b/libraries/avatars/src/ClientTraitsHandler.cpp index e133f178df..c2576bea2e 100644 --- a/libraries/avatars/src/ClientTraitsHandler.cpp +++ b/libraries/avatars/src/ClientTraitsHandler.cpp @@ -28,7 +28,8 @@ ClientTraitsHandler::ClientTraitsHandler(AvatarData* owningAvatar) : } }); - nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, this, "processTraitOverride"); + nodeList->getPacketReceiver().registerListener(PacketType::SetAvatarTraits, + PacketReceiver::makeSourcedListenerReference(this, &ClientTraitsHandler::processTraitOverride)); } void ClientTraitsHandler::markTraitUpdated(AvatarTraits::TraitType updatedTrait) { diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index aaaf7d645a..3da019ce06 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -26,7 +26,8 @@ EntityEditPacketSender::EntityEditPacketSender() { auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerDirectListener(PacketType::EntityEditNack, this, "processEntityEditNackPacket"); + packetReceiver.registerDirectListener(PacketType::EntityEditNack, + PacketReceiver::makeSourcedListenerReference(this, &EntityEditPacketSender::processEntityEditNackPacket)); } void EntityEditPacketSender::processEntityEditNackPacket(QSharedPointer message, SharedNodePointer sendingNode) { diff --git a/libraries/entities/src/EntityScriptServerLogClient.cpp b/libraries/entities/src/EntityScriptServerLogClient.cpp index 5853c9585e..5d7d4017cd 100644 --- a/libraries/entities/src/EntityScriptServerLogClient.cpp +++ b/libraries/entities/src/EntityScriptServerLogClient.cpp @@ -14,7 +14,8 @@ EntityScriptServerLogClient::EntityScriptServerLogClient() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::EntityServerScriptLog, this, "handleEntityServerScriptLogPacket"); + packetReceiver.registerListener(PacketType::EntityServerScriptLog, + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptServerLogClient::handleEntityServerScriptLogPacket)); QObject::connect(nodeList.data(), &NodeList::nodeActivated, this, &EntityScriptServerLogClient::nodeActivated); QObject::connect(nodeList.data(), &NodeList::nodeKilled, this, &EntityScriptServerLogClient::nodeKilled); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index fd83c99ca5..05947551ba 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -59,7 +59,8 @@ EntityScriptingInterface::EntityScriptingInterface(bool bidOnSimulationOwnership connect(nodeList.data(), &NodeList::canGetAndSetPrivateUserDataChanged, this, &EntityScriptingInterface::canGetAndSetPrivateUserDataChanged); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::EntityScriptCallMethod, this, "handleEntityScriptCallMethodPacket"); + packetReceiver.registerListener(PacketType::EntityScriptCallMethod, + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptingInterface::handleEntityScriptCallMethodPacket)); } void EntityScriptingInterface::queueEntityMessage(PacketType packetType, diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 44f42caec2..bbd743cf95 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -43,10 +43,14 @@ AssetClient::AssetClient() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::AssetMappingOperationReply, this, "handleAssetMappingOperationReply"); - packetReceiver.registerListener(PacketType::AssetGetInfoReply, this, "handleAssetGetInfoReply"); - packetReceiver.registerListener(PacketType::AssetGetReply, this, "handleAssetGetReply", true); - packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply"); + packetReceiver.registerListener(PacketType::AssetMappingOperationReply, + PacketReceiver::makeSourcedListenerReference(this, &AssetClient::handleAssetMappingOperationReply)); + packetReceiver.registerListener(PacketType::AssetGetInfoReply, + PacketReceiver::makeSourcedListenerReference(this, &AssetClient::handleAssetGetInfoReply)); + packetReceiver.registerListener(PacketType::AssetGetReply, + PacketReceiver::makeSourcedListenerReference(this, &AssetClient::handleAssetGetReply), true); + packetReceiver.registerListener(PacketType::AssetUploadReply, + PacketReceiver::makeSourcedListenerReference(this, &AssetClient::handleAssetUploadReply)); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &AssetClient::handleNodeKilled); connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset, diff --git a/libraries/networking/src/EntityScriptClient.cpp b/libraries/networking/src/EntityScriptClient.cpp index 1eab5bf2d7..fb98e8042b 100644 --- a/libraries/networking/src/EntityScriptClient.cpp +++ b/libraries/networking/src/EntityScriptClient.cpp @@ -34,7 +34,8 @@ EntityScriptClient::EntityScriptClient() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply, this, "handleGetScriptStatusReply"); + packetReceiver.registerListener(PacketType::EntityScriptGetStatusReply, + PacketReceiver::makeSourcedListenerReference(this, &EntityScriptClient::handleGetScriptStatusReply)); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &EntityScriptClient::handleNodeKilled); connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset, diff --git a/libraries/networking/src/MessagesClient.cpp b/libraries/networking/src/MessagesClient.cpp index d6f9d041ea..c883935cb4 100644 --- a/libraries/networking/src/MessagesClient.cpp +++ b/libraries/networking/src/MessagesClient.cpp @@ -28,7 +28,8 @@ MessagesClient::MessagesClient() { }); auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); - packetReceiver.registerListener(PacketType::MessagesData, this, "handleMessagesPacket"); + packetReceiver.registerListener(PacketType::MessagesData, + PacketReceiver::makeSourcedListenerReference(this, &MessagesClient::handleMessagesPacket)); connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated); } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index e02a8dd56e..076e94522f 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -139,20 +139,34 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) startSTUNPublicSocketUpdate(); auto& packetReceiver = getPacketReceiver(); - packetReceiver.registerListener(PacketType::DomainList, this, "processDomainServerList"); - packetReceiver.registerListener(PacketType::Ping, this, "processPingPacket"); - packetReceiver.registerListener(PacketType::PingReply, this, "processPingReplyPacket"); - packetReceiver.registerListener(PacketType::ICEPing, this, "processICEPingPacket"); - packetReceiver.registerListener(PacketType::DomainServerAddedNode, this, "processDomainServerAddedNode"); - packetReceiver.registerListener(PacketType::DomainServerConnectionToken, this, "processDomainServerConnectionTokenPacket"); - packetReceiver.registerListener(PacketType::DomainConnectionDenied, &_domainHandler, "processDomainServerConnectionDeniedPacket"); - packetReceiver.registerListener(PacketType::DomainSettings, &_domainHandler, "processSettingsPacketList"); - packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_domainHandler, "processICEResponsePacket"); - packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, &_domainHandler, "processDTLSRequirementPacket"); - packetReceiver.registerListener(PacketType::ICEPingReply, &_domainHandler, "processICEPingReplyPacket"); - packetReceiver.registerListener(PacketType::DomainServerPathResponse, this, "processDomainServerPathResponse"); - packetReceiver.registerListener(PacketType::DomainServerRemovedNode, this, "processDomainServerRemovedNode"); - packetReceiver.registerListener(PacketType::UsernameFromIDReply, this, "processUsernameFromIDReply"); + packetReceiver.registerListener(PacketType::DomainList, + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processDomainServerList)); + packetReceiver.registerListener(PacketType::Ping, + PacketReceiver::makeSourcedListenerReference(this, &NodeList::processPingPacket)); + packetReceiver.registerListener(PacketType::PingReply, + PacketReceiver::makeSourcedListenerReference(this, &NodeList::processPingReplyPacket)); + packetReceiver.registerListener(PacketType::ICEPing, + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processICEPingPacket)); + packetReceiver.registerListener(PacketType::DomainServerAddedNode, + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processDomainServerAddedNode)); + packetReceiver.registerListener(PacketType::DomainServerConnectionToken, + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processDomainServerConnectionTokenPacket)); + packetReceiver.registerListener(PacketType::DomainConnectionDenied, + PacketReceiver::makeUnsourcedListenerReference(&_domainHandler, &DomainHandler::processDomainServerConnectionDeniedPacket)); + packetReceiver.registerListener(PacketType::DomainSettings, + PacketReceiver::makeUnsourcedListenerReference(&_domainHandler, &DomainHandler::processSettingsPacketList)); + packetReceiver.registerListener(PacketType::ICEServerPeerInformation, + PacketReceiver::makeUnsourcedListenerReference(&_domainHandler, &DomainHandler::processICEResponsePacket)); + packetReceiver.registerListener(PacketType::DomainServerRequireDTLS, + PacketReceiver::makeUnsourcedListenerReference(&_domainHandler, &DomainHandler::processDTLSRequirementPacket)); + packetReceiver.registerListener(PacketType::ICEPingReply, + PacketReceiver::makeUnsourcedListenerReference(&_domainHandler, &DomainHandler::processICEPingReplyPacket)); + packetReceiver.registerListener(PacketType::DomainServerPathResponse, + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processDomainServerPathResponse)); + packetReceiver.registerListener(PacketType::DomainServerRemovedNode, + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processDomainServerRemovedNode)); + packetReceiver.registerListener(PacketType::UsernameFromIDReply, + PacketReceiver::makeUnsourcedListenerReference(this, &NodeList::processUsernameFromIDReply)); } qint64 NodeList::sendStats(QJsonObject statsObject, HifiSockAddr destination) { diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 962ceab00f..80222d38a9 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -12,7 +12,8 @@ #include "PacketReceiver.h" -#include +#include +#include #include "DependencyManager.h" #include "NetworkLogging.h" @@ -25,85 +26,56 @@ PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) { qRegisterMetaType>(); } -bool PacketReceiver::registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot) { +bool PacketReceiver::ListenerReference::invokeWithQt(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode) { + return QMetaObject::invokeMethod(getObject(), [=]() { + this->invokeDirectly(receivedMessagePointer, sourceNode); + }); +} + +bool PacketReceiver::registerListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener) { Q_ASSERT_X(!types.empty(), "PacketReceiver::registerListenerForTypes", "No types to register"); - Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No object to register"); - Q_ASSERT_X(slot, "PacketReceiver::registerListenerForTypes", "No slot to register"); + Q_ASSERT_X(listener, "PacketReceiver::registerListenerForTypes", "No listener to register"); - // Partition types based on whether they are sourced or not (non sourced in front) - auto middle = std::partition(std::begin(types), std::end(types), [](PacketType type) { - return PacketTypeEnum::getNonSourcedPackets().contains(type); - }); - - QMetaMethod nonSourcedMethod, sourcedMethod; - - // Check we have a valid method for non sourced types if any - if (middle != std::begin(types)) { - nonSourcedMethod = matchingMethodForListener(*std::begin(types), listener, slot); - if (!nonSourcedMethod.isValid()) { - return false; - } - } - - // Check we have a valid method for sourced types if any - if (middle != std::end(types)) { - sourcedMethod = matchingMethodForListener(*middle, listener, slot); - if (!sourcedMethod.isValid()) { - return false; - } - } - - // Register non sourced types - std::for_each(std::begin(types), middle, [this, &listener, &nonSourcedMethod](PacketType type) { - registerVerifiedListener(type, listener, nonSourcedMethod); - }); - - // Register sourced types - std::for_each(middle, std::end(types), [this, &listener, &sourcedMethod](PacketType type) { - registerVerifiedListener(type, listener, sourcedMethod); + std::for_each(std::begin(types), std::end(types), [this, &listener](PacketType type) { + registerVerifiedListener(type, listener); }); return true; } -void PacketReceiver::registerDirectListener(PacketType type, QObject* listener, const char* slot) { - Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No object to register"); - Q_ASSERT_X(slot, "PacketReceiver::registerDirectListener", "No slot to register"); +void PacketReceiver::registerDirectListener(PacketType type, const ListenerReferencePointer& listener) { + Q_ASSERT_X(listener, "PacketReceiver::registerDirectListener", "No listener to register"); - bool success = registerListener(type, listener, slot); + bool success = registerListener(type, listener); if (success) { QMutexLocker locker(&_directConnectSetMutex); // if we successfully registered, add this object to the set of objects that are directly connected - _directlyConnectedObjects.insert(listener); + _directlyConnectedObjects.insert(listener->getObject()); } } -void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types, - QObject* listener, const char* slot) { - Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No object to register"); - Q_ASSERT_X(slot, "PacketReceiver::registerDirectListenerForTypes", "No slot to register"); +void PacketReceiver::registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener) { + Q_ASSERT_X(listener, "PacketReceiver::registerDirectListenerForTypes", "No listener to register"); // just call register listener for types to start - bool success = registerListenerForTypes(std::move(types), listener, slot); + bool success = registerListenerForTypes(std::move(types), listener); if (success) { QMutexLocker locker(&_directConnectSetMutex); // if we successfully registered, add this object to the set of objects that are directly connected - _directlyConnectedObjects.insert(listener); + _directlyConnectedObjects.insert(listener->getObject()); } } -bool PacketReceiver::registerListener(PacketType type, QObject* listener, const char* slot, - bool deliverPending) { - Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No object to register"); - Q_ASSERT_X(slot, "PacketReceiver::registerListener", "No slot to register"); +bool PacketReceiver::registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) { + Q_ASSERT_X(listener, "PacketReceiver::registerListener", "No listener to register"); - QMetaMethod matchingMethod = matchingMethodForListener(type, listener, slot); + bool matchingMethod = matchingMethodForListener(type, listener); - if (matchingMethod.isValid()) { + if (matchingMethod) { qCDebug(networking) << "Registering a packet listener for packet list type" << type; - registerVerifiedListener(type, listener, matchingMethod, deliverPending); + registerVerifiedListener(type, listener, deliverPending); return true; } else { qCWarning(networking) << "FAILED to Register a packet listener for packet list type" << type; @@ -111,62 +83,23 @@ bool PacketReceiver::registerListener(PacketType type, QObject* listener, const } } -QMetaMethod PacketReceiver::matchingMethodForListener(PacketType type, QObject* object, const char* slot) const { - Q_ASSERT_X(object, "PacketReceiver::matchingMethodForListener", "No object to call"); - Q_ASSERT_X(slot, "PacketReceiver::matchingMethodForListener", "No slot to call"); +bool PacketReceiver::matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const { + Q_ASSERT_X(listener, "PacketReceiver::matchingMethodForListener", "No listener to call"); - // normalize the slot with the expected parameters - static const QString SIGNATURE_TEMPLATE("%1(%2)"); - static const QString NON_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer"; + bool isSourced = listener->isSourced(); + bool isNonSourcedPacket = PacketTypeEnum::getNonSourcedPackets().contains(type); - QSet possibleSignatures { - SIGNATURE_TEMPLATE.arg(slot, NON_SOURCED_MESSAGE_LISTENER_PARAMETERS) - }; - - if (!PacketTypeEnum::getNonSourcedPackets().contains(type)) { - static const QString SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer,QSharedPointer"; - static const QString TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS = "QSharedPointer,SharedNodePointer"; - - // a sourced packet must take the shared pointer to the ReceivedMessage but optionally could include - // a shared pointer to the node - possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, TYPEDEF_SOURCED_MESSAGE_LISTENER_PARAMETERS); - possibleSignatures << SIGNATURE_TEMPLATE.arg(slot, SOURCED_MESSAGE_LISTENER_PARAMETERS); + assert(!isSourced || !isNonSourcedPacket); + if (isSourced && isNonSourcedPacket) { + qCDebug(networking) << "PacketReceiver::registerListener cannot support a sourced listener for type" << type; + return false; } - int methodIndex = -1; - - foreach(const QString& signature, possibleSignatures) { - QByteArray normalizedSlot = - QMetaObject::normalizedSignature(signature.toStdString().c_str()); - - // does the constructed normalized method exist? - methodIndex = object->metaObject()->indexOfSlot(normalizedSlot.toStdString().c_str()); - - if (methodIndex >= 0) { - break; - } - } - - if (methodIndex < 0) { - qCDebug(networking) << "PacketReceiver::registerListener expected a slot with one of the following signatures:" - << possibleSignatures.toList() << "- but such a slot was not found." - << "Could not complete listener registration for type" << type; - } - - Q_ASSERT(methodIndex >= 0); - - // return the converted QMetaMethod - if (methodIndex >= 0) { - return object->metaObject()->method(methodIndex); - } else { - // if somehow (scripting?) something bad gets in here at runtime that doesn't hit the asserts above - // return a non-valid QMetaMethod - return QMetaMethod(); - } + return true; } -void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, const QMetaMethod& slot, bool deliverPending) { - Q_ASSERT_X(object, "PacketReceiver::registerVerifiedListener", "No object to register"); +void PacketReceiver::registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending) { + Q_ASSERT_X(listener, "PacketReceiver::registerVerifiedListener", "No listener to register"); QMutexLocker locker(&_packetListenerLock); if (_messageListenerMap.contains(type)) { @@ -175,7 +108,7 @@ void PacketReceiver::registerVerifiedListener(PacketType type, QObject* object, } // add the mapping - _messageListenerMap[type] = { QPointer(object), slot, deliverPending }; + _messageListenerMap[type] = { listener, deliverPending }; } void PacketReceiver::unregisterListener(QObject* listener) { @@ -188,7 +121,7 @@ void PacketReceiver::unregisterListener(QObject* listener) { auto it = _messageListenerMap.begin(); while (it != _messageListenerMap.end()) { - if (it.value().object == listener) { + if (it.value().listener->getObject() == listener) { it = _messageListenerMap.erase(it); } else { ++it; @@ -261,7 +194,7 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei QMutexLocker packetListenerLocker(&_packetListenerLock); auto it = _messageListenerMap.find(receivedMessage->getType()); - if (it != _messageListenerMap.end() && it->method.isValid()) { + if (it != _messageListenerMap.end() && !it->listener.isNull()) { auto listener = it.value(); @@ -271,36 +204,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei bool success = false; - Qt::ConnectionType connectionType; + bool isDirectConnect = false; // check if this is a directly connected listener { QMutexLocker directConnectLocker(&_directConnectSetMutex); - connectionType = _directlyConnectedObjects.contains(listener.object) ? Qt::DirectConnection : Qt::AutoConnection; + isDirectConnect = _directlyConnectedObjects.contains(listener.listener->getObject()); } - QMetaMethod metaMethod = listener.method; - - static const QByteArray QSHAREDPOINTER_NODE_NORMALIZED = QMetaObject::normalizedType("QSharedPointer"); - static const QByteArray SHARED_NODE_NORMALIZED = QMetaObject::normalizedType("SharedNodePointer"); - // one final check on the QPointer before we go to invoke - if (listener.object) { - if (metaMethod.parameterTypes().contains(SHARED_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.object, - connectionType, - Q_ARG(QSharedPointer, receivedMessage), - Q_ARG(SharedNodePointer, matchingNode)); - - } else if (metaMethod.parameterTypes().contains(QSHAREDPOINTER_NODE_NORMALIZED)) { - success = metaMethod.invoke(listener.object, - connectionType, - Q_ARG(QSharedPointer, receivedMessage), - Q_ARG(QSharedPointer, matchingNode)); - + if (listener.listener->getObject()) { + if (isDirectConnect) { + success = listener.listener->invokeDirectly(receivedMessage, matchingNode); } else { - success = metaMethod.invoke(listener.object, - connectionType, - Q_ARG(QSharedPointer, receivedMessage)); + success = listener.listener->invokeWithQt(receivedMessage, matchingNode); } } else { qCDebug(networking).nospace() << "Listener for packet " << receivedMessage->getType() @@ -310,19 +226,19 @@ void PacketReceiver::handleVerifiedMessage(QSharedPointer recei // if it exists, remove the listener from _directlyConnectedObjects { QMutexLocker directConnectLocker(&_directConnectSetMutex); - _directlyConnectedObjects.remove(listener.object); + _directlyConnectedObjects.remove(listener.listener->getObject()); } } if (!success) { qCDebug(networking).nospace() << "Error delivering packet " << receivedMessage->getType() << " to listener " - << listener.object << "::" << qPrintable(listener.method.methodSignature()); + << listener.listener->getObject(); } } else if (it == _messageListenerMap.end()) { qCWarning(networking) << "No listener found for packet type" << receivedMessage->getType(); // insert a dummy listener so we don't print this again - _messageListenerMap.insert(receivedMessage->getType(), { nullptr, QMetaMethod(), false }); + _messageListenerMap.insert(receivedMessage->getType(), { ListenerReferencePointer(), false }); } } diff --git a/libraries/networking/src/PacketReceiver.h b/libraries/networking/src/PacketReceiver.h index e29a0d6e5a..0bed88cff9 100644 --- a/libraries/networking/src/PacketReceiver.h +++ b/libraries/networking/src/PacketReceiver.h @@ -16,8 +16,6 @@ #include #include -#include -#include #include #include #include @@ -29,6 +27,7 @@ #include "udt/PacketHeaders.h" class EntityEditPacketSender; +class Node; class OctreePacketProcessor; namespace std { @@ -42,6 +41,22 @@ namespace std { class PacketReceiver : public QObject { Q_OBJECT +public: + class ListenerReference { + public: + virtual bool invokeDirectly(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode) = 0; + bool invokeWithQt(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode); + virtual bool isSourced() const = 0; + virtual QObject* getObject() const = 0; + }; + typedef QSharedPointer ListenerReferencePointer; + + template + static ListenerReferencePointer makeUnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer)); + + template + static ListenerReferencePointer makeSourcedListenerReference(T* target, void (T::*slot)(QSharedPointer, QSharedPointer)); + public: using PacketTypeList = std::vector; @@ -55,8 +70,8 @@ public: // If deliverPending is false, ReceivedMessage will only be delivered once all packets for the message have // been received. If deliverPending is true, ReceivedMessage will be delivered as soon as the first packet // for the message is received. - bool registerListener(PacketType type, QObject* listener, const char* slot, bool deliverPending = false); - bool registerListenerForTypes(PacketTypeList types, QObject* listener, const char* slot); + bool registerListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false); + bool registerListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener); void unregisterListener(QObject* listener); void handleVerifiedPacket(std::unique_ptr packet); @@ -64,9 +79,34 @@ public: void handleMessageFailure(HifiSockAddr from, udt::Packet::MessageNumber messageNumber); private: + template + class UnsourcedListenerReference : public ListenerReference { + public: + inline UnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer)); + virtual bool invokeDirectly(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode); + virtual bool isSourced() const { return false; } + virtual QObject* getObject() const { return _target; } + + private: + QPointer _target; + void (T::*_slot)(QSharedPointer); + }; + + template + class SourcedListenerReference : public ListenerReference { + public: + inline SourcedListenerReference(T* target, void (T::*slot)(QSharedPointer, QSharedPointer)); + virtual bool invokeDirectly(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode); + virtual bool isSourced() const { return true; } + virtual QObject* getObject() const { return _target; } + + private: + QPointer _target; + void (T::*_slot)(QSharedPointer, QSharedPointer); + }; + struct Listener { - QPointer object; - QMetaMethod method; + ListenerReferencePointer listener; bool deliverPending; }; @@ -74,11 +114,11 @@ private: // these are brutal hacks for now - ideally GenericThread / ReceivedPacketProcessor // should be changed to have a true event loop and be able to handle our QMetaMethod::invoke - void registerDirectListenerForTypes(PacketTypeList types, QObject* listener, const char* slot); - void registerDirectListener(PacketType type, QObject* listener, const char* slot); + void registerDirectListenerForTypes(PacketTypeList types, const ListenerReferencePointer& listener); + void registerDirectListener(PacketType type, const ListenerReferencePointer& listener); - QMetaMethod matchingMethodForListener(PacketType type, QObject* object, const char* slot) const; - void registerVerifiedListener(PacketType type, QObject* listener, const QMetaMethod& slot, bool deliverPending = false); + bool matchingMethodForListener(PacketType type, const ListenerReferencePointer& listener) const; + void registerVerifiedListener(PacketType type, const ListenerReferencePointer& listener, bool deliverPending = false); QMutex _packetListenerLock; QHash _messageListenerMap; @@ -93,4 +133,42 @@ private: friend class OctreePacketProcessor; }; +template +PacketReceiver::ListenerReferencePointer PacketReceiver::makeUnsourcedListenerReference(T* target, void (T::* slot)(QSharedPointer)) { + return QSharedPointer>::create(target, slot); +} + +template +PacketReceiver::ListenerReferencePointer PacketReceiver::makeSourcedListenerReference(T* target, void (T::* slot)(QSharedPointer, QSharedPointer)) { + return QSharedPointer>::create(target, slot); +} + +template +PacketReceiver::UnsourcedListenerReference::UnsourcedListenerReference(T* target, void (T::*slot)(QSharedPointer)) : + _target(target),_slot(slot) { +} + +template +bool PacketReceiver::UnsourcedListenerReference::invokeDirectly(const QSharedPointer& receivedMessagePointer, const QSharedPointer&) { + if (_target.isNull()) { + return false; + } + (_target->*_slot)(receivedMessagePointer); + return true; +} + +template +PacketReceiver::SourcedListenerReference::SourcedListenerReference(T* target, void (T::*slot)(QSharedPointer, QSharedPointer)) : + _target(target),_slot(slot) { +} + +template +bool PacketReceiver::SourcedListenerReference::invokeDirectly(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode) { + if (_target.isNull()) { + return false; + } + (_target->*_slot)(receivedMessagePointer, sourceNode); + return true; +} + #endif // hifi_PacketReceiver_h diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 88a4f5bf32..4ba456d859 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -16,8 +16,9 @@ #include #include -#include -#include +#include +#include +#include #include #include diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index dd2cc12d50..c4f24c552b 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -64,7 +64,8 @@ void OctreePersistThread::start() { cleanupOldReplacementBackups(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListener(PacketType::OctreeDataFileReply, this, "handleOctreeDataFileReply"); + packetReceiver.registerListener(PacketType::OctreeDataFileReply, + PacketReceiver::makeUnsourcedListenerReference(this, &OctreePersistThread::handleOctreeDataFileReply)); auto nodeList = DependencyManager::get(); const DomainHandler& domainHandler = nodeList->getDomainHandler(); From 34e3d9dd2b9b96a8a809e87a4d70a542c8738d08 Mon Sep 17 00:00:00 2001 From: Heather Anderson Date: Sun, 9 Aug 2020 15:44:41 -0700 Subject: [PATCH 045/388] improved listener lifetime controls (in case a deferred invoke takes longer than expected) --- libraries/networking/src/PacketReceiver.cpp | 3 ++- libraries/networking/src/PacketReceiver.h | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/PacketReceiver.cpp b/libraries/networking/src/PacketReceiver.cpp index 80222d38a9..c13fb8566b 100644 --- a/libraries/networking/src/PacketReceiver.cpp +++ b/libraries/networking/src/PacketReceiver.cpp @@ -27,8 +27,9 @@ PacketReceiver::PacketReceiver(QObject* parent) : QObject(parent) { } bool PacketReceiver::ListenerReference::invokeWithQt(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode) { + ListenerReferencePointer thisPointer = sharedFromThis(); return QMetaObject::invokeMethod(getObject(), [=]() { - this->invokeDirectly(receivedMessagePointer, sourceNode); + thisPointer->invokeDirectly(receivedMessagePointer, sourceNode); }); } diff --git a/libraries/networking/src/PacketReceiver.h b/libraries/networking/src/PacketReceiver.h index 0bed88cff9..071638cda9 100644 --- a/libraries/networking/src/PacketReceiver.h +++ b/libraries/networking/src/PacketReceiver.h @@ -20,6 +20,8 @@ #include #include #include +#include +#include #include "NLPacket.h" #include "NLPacketList.h" @@ -42,7 +44,7 @@ namespace std { class PacketReceiver : public QObject { Q_OBJECT public: - class ListenerReference { + class ListenerReference : public QEnableSharedFromThis { public: virtual bool invokeDirectly(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode) = 0; bool invokeWithQt(const QSharedPointer& receivedMessagePointer, const QSharedPointer& sourceNode); From 2cd1fc84711c01716a179ddcb1657cc8de472302 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Tue, 11 Aug 2020 22:50:12 -0400 Subject: [PATCH 046/388] Keep the suffix only for Duplicate This update removes the incremental suffix from the "Paste" since this can't really work. This will be only for "Duplicate". --- scripts/system/create/entitySelectionTool/entitySelectionTool.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/create/entitySelectionTool/entitySelectionTool.js b/scripts/system/create/entitySelectionTool/entitySelectionTool.js index cbfaf7f5a6..e224e28fca 100644 --- a/scripts/system/create/entitySelectionTool/entitySelectionTool.js +++ b/scripts/system/create/entitySelectionTool/entitySelectionTool.js @@ -528,7 +528,6 @@ SelectionManager = (function() { } else { delete properties.position; } - properties.name = getDuplicateAppendedName(properties.name); copiedProperties.push(properties); }); From 21167a0f6a6faf14c9948e0c6821313fb261402c Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Fri, 14 Aug 2020 23:53:01 -0400 Subject: [PATCH 047/388] Maybe fix scriptURL on WebEntities bug --- libraries/entities-renderer/src/RenderableWebEntityItem.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 1da4999bad..121752d6c0 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -207,7 +207,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene { auto scriptURL = entity->getScriptURL(); if (_scriptURL != scriptURL) { - _webSurface->getRootItem()->setProperty("scriptURL", _scriptURL); + _webSurface->getRootItem()->setProperty("scriptURL", scriptURL); _scriptURL = scriptURL; } } From cd5bdf6160ada2ff905226c9ccddaf1507bfc4c6 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Sat, 15 Aug 2020 00:56:19 -0400 Subject: [PATCH 048/388] Added transparent + colored background web entity capability dynamically. --- interface/resources/qml/Web3DSurface.qml | 19 +++++++++++----- .../+webengine/FlickableWebViewCore.qml | 6 ++++- .../qml/controls/FlickableWebViewCore.qml | 2 ++ interface/resources/qml/controls/WebView.qml | 1 + .../src/RenderableWebEntityItem.cpp | 9 ++++++++ .../src/RenderableWebEntityItem.h | 2 ++ .../entities/src/EntityItemProperties.cpp | 12 ++++++++++ libraries/entities/src/EntityItemProperties.h | 2 ++ libraries/entities/src/EntityPropertyFlags.h | 2 ++ libraries/entities/src/WebEntityItem.cpp | 22 +++++++++++++++++-- libraries/entities/src/WebEntityItem.h | 6 +++++ 11 files changed, 75 insertions(+), 8 deletions(-) diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 39ee85444d..92ceba6a8d 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -18,22 +18,31 @@ Item { anchors.fill: parent property string url: "" property string scriptUrl: null + property string webBackgroundColor: "#FFFFFFFF" onUrlChanged: { - load(root.url, root.scriptUrl); + load(root.url, root.scriptUrl, root.webBackgroundColor); } onScriptUrlChanged: { if (root.item) { root.item.scriptUrl = root.scriptUrl; } else { - load(root.url, root.scriptUrl); + load(root.url, root.scriptUrl, root.webBackgroundColor); + } + } + + onWebBackgroundColorChanged: { + if (root.item) { + root.item.webBackgroundColor = root.webBackgroundColor; + } else { + load(root.url, root.scriptUrl, root.webBackgroundColor); } } property var item: null - function load(url, scriptUrl) { + function load(url, scriptUrl, webBackgroundColor) { // Ensure we reset any existing item to "about:blank" to ensure web audio stops: DEV-2375 if (root.item != null) { root.item.url = "about:blank" @@ -44,12 +53,12 @@ Item { root.item = newItem root.item.url = url root.item.scriptUrl = scriptUrl - root.item.transparentBackground = true + root.item.transparentBackground = webBackgroundColor.endsWith("FF") ? true : false }) } Component.onCompleted: { - load(root.url, root.scriptUrl); + load(root.url, root.scriptUrl, root.webBackgroundColor); } signal sendToScript(var message); diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index 6fbfcfde1f..93126152a2 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -15,6 +15,7 @@ Item { property alias webViewCore: webViewCore property alias webViewCoreProfile: webViewCore.profile property string webViewCoreUserAgent + property string webBackgroundColor: "#FFFFFFFF" // Fully opaque white. property string userScriptUrl: "" property string urlTag: "noDownload=false"; @@ -98,7 +99,10 @@ Item { width: parent.width height: parent.height - backgroundColor: "transparent" + //backgroundColor: "transparent" + //backgroundColor: "#FFFF00CC" + backgroundColor: flick.webBackgroundColor + //backgroundColor: Qt.rgba(0.502, 0.502, 0.502, 0.502) profile: HFWebEngineProfile; settings.pluginsEnabled: true diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index a844c8b624..cccd42457a 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -14,6 +14,8 @@ Item { property alias webViewCoreProfile: webViewCore.profile property string webViewCoreUserAgent + property string webBackgroundColor: "#FFFFFFFF" + property string userScriptUrl: "" property string urlTag: "noDownload=false"; diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 6b57ab85cd..8b103c5ef2 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -23,6 +23,7 @@ Item { property bool passwordField: false property alias flickable: webroot.interactive property alias blurOnCtrlShift: webroot.blurOnCtrlShift + property alias webBackgroundColor: webroot.webBackgroundColor function stop() { webroot.stop(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 121752d6c0..b3d1edcfb5 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -226,6 +227,14 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } } + { + auto webBackgroundColor = entity->getWebBackgroundColor(); + if (_webBackgroundColor != webBackgroundColor) { + _webSurface->getRootItem()->setProperty("webBackgroundColor", webBackgroundColor); + _webBackgroundColor = webBackgroundColor; + } + } + { auto contextPosition = entity->getWorldPosition(); if (_contextPosition != contextPosition) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 7118774d30..786df55ee5 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -93,6 +94,7 @@ private: uint16_t _dpi; QString _scriptURL; uint8_t _maxFPS; + QString _webBackgroundColor; WebInputMode _inputMode; glm::vec3 _contextPosition; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index d671d46c22..294ac6228e 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -4,6 +4,7 @@ // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -601,6 +602,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MAX_FPS, maxFPS); CHECK_PROPERTY_CHANGE(PROP_INPUT_MODE, inputMode); CHECK_PROPERTY_CHANGE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); + CHECK_PROPERTY_CHANGE(PROP_WEB_BACKGROUND_COLOR, webBackgroundColor); // Polyline CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); @@ -1819,6 +1821,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_FPS, maxFPS); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_INPUT_MODE, inputMode, getInputModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_WEB_BACKGROUND_COLOR, webBackgroundColor); } // PolyVoxel only @@ -2200,6 +2203,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(maxFPS, uint8_t, setMaxFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(inputMode, InputMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(showKeyboardFocusHighlight, bool, setShowKeyboardFocusHighlight); + COPY_PROPERTY_FROM_QSCRIPTVALUE(webBackgroundColor, QString, setWebBackgroundColor); // Polyline COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); @@ -2491,6 +2495,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(maxFPS); COPY_PROPERTY_IF_CHANGED(inputMode); COPY_PROPERTY_IF_CHANGED(showKeyboardFocusHighlight); + COPY_PROPERTY_IF_CHANGED(webBackgroundColor); // Polyline COPY_PROPERTY_IF_CHANGED(linePoints); @@ -2890,6 +2895,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t); ADD_PROPERTY_TO_MAP(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode); ADD_PROPERTY_TO_MAP(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool); + ADD_PROPERTY_TO_MAP(PROP_WEB_BACKGROUND_COLOR, WebBackgroundColor, webBackgroundColor, QString); // Polyline ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); @@ -3320,6 +3326,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, properties.getMaxFPS()); APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)properties.getInputMode()); APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, properties.getShowKeyboardFocusHighlight()); + APPEND_ENTITY_PROPERTY(PROP_WEB_BACKGROUND_COLOR, properties.getWebBackgroundColor()); } if (properties.getType() == EntityTypes::Line) { @@ -3795,6 +3802,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_FPS, uint8_t, setMaxFPS); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INPUT_MODE, WebInputMode, setInputMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_WEB_BACKGROUND_COLOR, QString, setWebBackgroundColor); } if (properties.getType() == EntityTypes::Line) { @@ -4182,6 +4190,7 @@ void EntityItemProperties::markAllChanged() { _maxFPSChanged = true; _inputModeChanged = true; _showKeyboardFocusHighlightChanged = true; + _webBackgroundColor = true; // Polyline _linePointsChanged = true; @@ -4872,6 +4881,9 @@ QList EntityItemProperties::listChangedProperties() { if (showKeyboardFocusHighlightChanged()) { out += "showKeyboardFocusHighlight"; } + if (webBackgroundColorChanged()) { + out += "webBackgroundColor"; + } // Shape if (shapeChanged()) { diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index efc8b5dc33..6049391ba6 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -4,6 +4,7 @@ // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -365,6 +366,7 @@ public: DEFINE_PROPERTY_REF(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t, WebEntityItem::DEFAULT_MAX_FPS); DEFINE_PROPERTY_REF_ENUM(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode, WebInputMode::TOUCH); DEFINE_PROPERTY_REF(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool, true); + DEFINE_PROPERTY_REF(PROP_WEB_BACKGROUND_COLOR, WebBackgroundColor, webBackgroundColor, QString, WebEntityItem::DEFAULT_WEB_BACKGROUND_COLOR); // Polyline DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index a7359c0bca..0e068544e3 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -4,6 +4,7 @@ // // Created by Brad Hefta-Gaub on 12/4/13. // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -317,6 +318,7 @@ enum EntityPropertyList { PROP_MAX_FPS = PROP_DERIVED_3, PROP_INPUT_MODE = PROP_DERIVED_4, PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT = PROP_DERIVED_5, + PROP_WEB_BACKGROUND_COLOR = PROP_DERIVED_6, // Polyline PROP_LINE_POINTS = PROP_DERIVED_0, diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index a62f599e4c..57b53bd911 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -22,8 +23,9 @@ #include "EntityTree.h" #include "EntityTreeElement.h" -const QString WebEntityItem::DEFAULT_SOURCE_URL = "http://www.google.com"; +const QString WebEntityItem::DEFAULT_SOURCE_URL = "https://www.vircadia.com"; const uint8_t WebEntityItem::DEFAULT_MAX_FPS = 10; +const QString WebEntityItem::DEFAULT_WEB_BACKGROUND_COLOR = "#FFFFFFFF"; EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new WebEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); @@ -60,6 +62,7 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxFPS, getMaxFPS); COPY_ENTITY_PROPERTY_TO_PROPERTIES(inputMode, getInputMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(showKeyboardFocusHighlight, getShowKeyboardFocusHighlight); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(webBackgroundColor, getWebBackgroundColor); return properties; } @@ -82,6 +85,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxFPS, setMaxFPS); SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(showKeyboardFocusHighlight, setShowKeyboardFocusHighlight); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(webBackgroundColor, setWebBackgroundColor); if (somethingChanged) { bool wantDebug = false; @@ -122,6 +126,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i READ_ENTITY_PROPERTY(PROP_MAX_FPS, uint8_t, setMaxFPS); READ_ENTITY_PROPERTY(PROP_INPUT_MODE, WebInputMode, setInputMode); READ_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); + READ_ENTITY_PROPERTY(PROP_WEB_BACKGROUND_COLOR, QString, setWebBackgroundColor); return bytesRead; } @@ -139,6 +144,7 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa requestedProperties += PROP_MAX_FPS; requestedProperties += PROP_INPUT_MODE; requestedProperties += PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT; + requestedProperties += PROP_WEB_BACKGROUND_COLOR; return requestedProperties; } @@ -165,6 +171,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, getMaxFPS()); APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)getInputMode()); APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, getShowKeyboardFocusHighlight()); + APPEND_ENTITY_PROPERTY(PROP_WEB_BACKGROUND_COLOR, getWebBackgroundColor()); } glm::vec3 WebEntityItem::getRaycastDimensions() const { @@ -307,7 +314,7 @@ void WebEntityItem::setScriptURL(const QString& value) { auto newURL = QUrl::fromUserInput(value); if (!newURL.isValid()) { - qCDebug(entities) << "Not setting web entity script URL since" << value << "cannot be parsed to a valid URL."; + qCDebug(entities) << "Not setting web entity script URL since" << value << "cannot be parsed to a valid URL."; return; } @@ -359,6 +366,17 @@ bool WebEntityItem::getShowKeyboardFocusHighlight() const { return _showKeyboardFocusHighlight; } +void WebEntityItem::setWebBackgroundColor(const QString& value) { + withWriteLock([&] { + _needsRenderUpdate |= _webBackgroundColor != value; + _webBackgroundColor = value; + }); +} + +QString WebEntityItem::getWebBackgroundColor() const { + return resultWithReadLock([&] { return _webBackgroundColor; }); +} + PulsePropertyGroup WebEntityItem::getPulseProperties() const { return resultWithReadLock([&] { return _pulseProperties; diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index b61e2b124f..25a0a162e5 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -1,6 +1,7 @@ // // Created by Bradley Austin Davis on 2015/05/12 // Copyright 2013 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -80,6 +81,10 @@ public: void setMaxFPS(uint8_t value); uint8_t getMaxFPS() const; + static const QString DEFAULT_WEB_BACKGROUND_COLOR; + void setWebBackgroundColor(const QString& value); + QString getWebBackgroundColor() const; + void setInputMode(const WebInputMode& value); WebInputMode getInputMode() const; @@ -98,6 +103,7 @@ protected: uint16_t _dpi; QString _scriptURL; uint8_t _maxFPS; + QString _webBackgroundColor; WebInputMode _inputMode; bool _showKeyboardFocusHighlight; bool _localSafeContext { false }; From 2590dfbaa72cdb6f6768db637cc9183082e51749 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Sat, 15 Aug 2020 00:57:10 -0400 Subject: [PATCH 049/388] Remove unused tests for backgroundColor. --- interface/resources/qml/+webengine/BrowserWebView.qml | 1 - interface/resources/qml/+webengine/QmlWebWindowView.qml | 1 - interface/resources/qml/controls/WebView.qml | 1 - 3 files changed, 3 deletions(-) diff --git a/interface/resources/qml/+webengine/BrowserWebView.qml b/interface/resources/qml/+webengine/BrowserWebView.qml index ad837ad043..a0525161f0 100644 --- a/interface/resources/qml/+webengine/BrowserWebView.qml +++ b/interface/resources/qml/+webengine/BrowserWebView.qml @@ -8,7 +8,6 @@ WebView { id: webview url: "https://vircadia.com/" profile: FileTypeProfile; - // backgroundColor: "transparent" property var parentRoot: null diff --git a/interface/resources/qml/+webengine/QmlWebWindowView.qml b/interface/resources/qml/+webengine/QmlWebWindowView.qml index dfabc631ba..84ab61ad28 100644 --- a/interface/resources/qml/+webengine/QmlWebWindowView.qml +++ b/interface/resources/qml/+webengine/QmlWebWindowView.qml @@ -10,7 +10,6 @@ Controls.WebView { anchors.fill: parent focus: true profile: HFWebEngineProfile; - // backgroundColor: "transparent" property string userScriptUrl: "" diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 8b103c5ef2..663bbf7867 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -43,7 +43,6 @@ Item { id: webroot width: parent.width height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height - // backgroundColor: "transparent" onLoadingChangedCallback: { keyboardRaised = false; From 1c457b766c5bd12c25e9428a0bc6b61f9bdb763c Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Sat, 15 Aug 2020 01:05:52 -0400 Subject: [PATCH 050/388] Use https://google.com/ to test transparency. --- scripts/test.html | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 scripts/test.html diff --git a/scripts/test.html b/scripts/test.html deleted file mode 100644 index 8fb6458eeb..0000000000 --- a/scripts/test.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - -

This is a test. (: (that doesn't work for some reason)

- - - From 433629163575109f9b0f4b54bca89a51b1b291ee Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Sat, 15 Aug 2020 01:33:18 -0400 Subject: [PATCH 051/388] Fix bool. --- interface/resources/qml/Web3DSurface.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 92ceba6a8d..3a3a2ef8b1 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -53,7 +53,7 @@ Item { root.item = newItem root.item.url = url root.item.scriptUrl = scriptUrl - root.item.transparentBackground = webBackgroundColor.endsWith("FF") ? true : false + root.item.transparentBackground = webBackgroundColor.endsWith("FF") ? false : true }) } From 70fd954ef492461ab6b720792a82ef68d0bb442d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Aug 2020 11:34:58 +1200 Subject: [PATCH 052/388] Replace the "HifiAbout" API with a new "About" API --- .../qml/hifi/dialogs/TabletAboutDialog.qml | 15 ++++---- .../qml/hifi/tablet/OpenVrConfiguration.qml | 2 +- interface/src/AboutUtil.h | 34 +++++++++++++++---- interface/src/Application.cpp | 9 +++-- scripts/system/interstitialPage.js | 2 +- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml index e7a9e0cef2..a943da32a0 100644 --- a/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml +++ b/interface/resources/qml/hifi/dialogs/TabletAboutDialog.qml @@ -3,6 +3,7 @@ // // Created by David Rowe on 18 Apr 2017 // Copyright 2017 High Fidelity, Inc. +// Copyright 2020 Vircadia contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -33,12 +34,12 @@ Rectangle { anchors.left: parent.left anchors.leftMargin: 70 RalewayRegular { - text: "Build " + HiFiAbout.buildVersion + text: "Build " + About.buildVersion size: 16 color: "white" } RalewayRegular { - text: "Released " + HiFiAbout.buildDate + text: "Released " + About.buildDate size: 16 color: "white" } @@ -56,7 +57,7 @@ Rectangle { text: "
Vircadia Github." size: 20 onLinkActivated: { - HiFiAbout.openUrl("https:/github.com/kasenvr/project-athena"); + About.openUrl("https:/github.com/kasenvr/project-athena"); } } @@ -70,13 +71,13 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - HiFiAbout.openUrl("https://www.qt.io/"); + About.openUrl("https://www.qt.io/"); } } } RalewayRegular { color: "white" - text: "Built using Qt " + HiFiAbout.qtVersion + text: "Built using Qt " + About.qtVersion size: 12 anchors.verticalCenter: parent.verticalCenter } @@ -102,7 +103,7 @@ Rectangle { MouseArea { anchors.fill: parent onClicked: { - HiFiAbout.openUrl("http://opus-codec.org/"); + About.openUrl("http://opus-codec.org/"); } } } @@ -131,7 +132,7 @@ Rectangle { text: "Distributed under the Apache License, Version 2.0.." size: 14 onLinkActivated: { - HiFiAbout.openUrl("http://www.apache.org/licenses/LICENSE-2.0.html"); + About.openUrl("http://www.apache.org/licenses/LICENSE-2.0.html"); } } } diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index f1275a39fb..6d5c388457 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -894,7 +894,7 @@ Flickable { hoverEnabled: true onEntered: privacyPolicyUnderline.visible = true; onExited: privacyPolicyUnderline.visible = false; - onClicked: HiFiAbout.openUrl("https://vircadia.com/privacy-policy"); + onClicked: About.openUrl("https://vircadia.com/privacy-policy"); } } diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index 4a5074857d..b4a318cf63 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -4,6 +4,7 @@ // // Created by Vlad Stelmahovsky on 15/5/2018. // Copyright 2018 High Fidelity, Inc. +// Copyright 2020 Vircadia Contributors. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html @@ -16,10 +17,10 @@ #include /**jsdoc - * The HifiAbout API provides information about the version of Interface that is currently running. It also - * has the functionality to open a web page in an Interface browser window. + * The About API provides information about the version of Interface that is currently running. It also has the + * functionality to open a web page in an Interface browser window. * - * @namespace HifiAbout + * @namespace About * * @hifi-interface * @hifi-client-entity @@ -30,9 +31,28 @@ * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. * * @example Report build information for the version of Interface currently running. - * print("HiFi build date: " + HifiAbout.buildDate); // Returns the build date of the version of Interface currently running on your machine. - * print("HiFi version: " + HifiAbout.buildVersion); // Returns the build version of Interface currently running on your machine. - * print("Qt version: " + HifiAbout.qtVersion); // Returns the Qt version details of the version of Interface currently running on your machine. + * print("Interface build date: " + About.buildDate); + * print("Interface version: " + About.buildVersion); + * print("Qt version: " + About.qtVersion); + */ + + /**jsdoc + * The HifiAbout API provides information about the version of Interface that is currently running. It also + * has the functionality to open a web page in an Interface browser window. + * + * @namespace HifiAbout + * + * @hifi-interface + * @hifi-client-entity + * @hifi-avatar + * + * @deprecated This API is deprecated and will be removed. Use the {@link About} API instead. + * + * @property {string} buildDate - The build date of Interface that is currently running. Read-only. + * @property {string} buildVersion - The build version of Interface that is currently running. Read-only. + * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. + * + * @borrows About.openUrl as openUrl */ class AboutUtil : public QObject { @@ -53,7 +73,7 @@ public slots: /**jsdoc * Display a web page in an Interface browser window. - * @function HifiAbout.openUrl + * @function About.openUrl * @param {string} url - The URL of the web page you want to view in Interface. */ void openUrl(const QString &url) const; diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc2aed7f53..80af7900ac 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -3512,7 +3512,8 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) { surfaceContext->setContextProperty("Selection", DependencyManager::get().data()); surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get().data()); surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); - surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); + surfaceContext->setContextProperty("About", AboutUtil::getInstance()); + surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); // Deprecated surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) { @@ -3623,7 +3624,8 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona surfaceContext->setContextProperty("Pointers", DependencyManager::get().data()); surfaceContext->setContextProperty("Window", DependencyManager::get().data()); surfaceContext->setContextProperty("Reticle", qApp->getApplicationCompositor().getReticleInterface()); - surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); + surfaceContext->setContextProperty("About", AboutUtil::getInstance()); + surfaceContext->setContextProperty("HiFiAbout", AboutUtil::getInstance()); // Deprecated. surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance()); @@ -7520,7 +7522,8 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get().data()); scriptEngine->registerGlobalObject("WalletScriptingInterface", DependencyManager::get().data()); scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get().data()); - scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); + scriptEngine->registerGlobalObject("About", AboutUtil::getInstance()); + scriptEngine->registerGlobalObject("HifiAbout", AboutUtil::getInstance()); // Deprecated. scriptEngine->registerGlobalObject("ResourceRequestObserver", DependencyManager::get().data()); registerInteractiveWindowMetaType(scriptEngine.data()); diff --git a/scripts/system/interstitialPage.js b/scripts/system/interstitialPage.js index 23cff7699a..0c238756a9 100644 --- a/scripts/system/interstitialPage.js +++ b/scripts/system/interstitialPage.js @@ -369,7 +369,7 @@ } } - var THE_PLACE = (HifiAbout.buildVersion === "dev") ? "hifi://TheSpot-dev" : "hifi://TheSpot"; + var THE_PLACE = (About.buildVersion === "dev") ? "hifi://TheSpot-dev" : "hifi://TheSpot"; function clickedOnOverlay(overlayID, event) { if (loadingToTheSpotHoverID === overlayID) { location.handleLookupString(THE_PLACE); From 66cdefa4b38de0b4f7c510db630bfece74a1f19d Mon Sep 17 00:00:00 2001 From: David Rowe Date: Mon, 17 Aug 2020 11:47:56 +1200 Subject: [PATCH 053/388] Add a new "About.platform" property --- interface/src/AboutUtil.h | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/interface/src/AboutUtil.h b/interface/src/AboutUtil.h index b4a318cf63..a602528154 100644 --- a/interface/src/AboutUtil.h +++ b/interface/src/AboutUtil.h @@ -26,11 +26,14 @@ * @hifi-client-entity * @hifi-avatar * + * @property {string} platform - The name of the Interface platform running, e,g., "Vircadia" for the Vircadia. + * Read-only. * @property {string} buildDate - The build date of Interface that is currently running. Read-only. * @property {string} buildVersion - The build version of Interface that is currently running. Read-only. * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. * - * @example Report build information for the version of Interface currently running. + * @example Report information on the version of Interface currently running. + * print("Interface platform: " + About.platform); * print("Interface build date: " + About.buildDate); * print("Interface version: " + About.buildVersion); * print("Qt version: " + About.qtVersion); @@ -48,6 +51,8 @@ * * @deprecated This API is deprecated and will be removed. Use the {@link About} API instead. * + * @property {string} platform - The name of the Interface platform running, e,g., "Vircadia" for the Vircadia. + * Read-only. * @property {string} buildDate - The build date of Interface that is currently running. Read-only. * @property {string} buildVersion - The build version of Interface that is currently running. Read-only. * @property {string} qtVersion - The Qt version used in Interface that is currently running. Read-only. @@ -58,6 +63,7 @@ class AboutUtil : public QObject { Q_OBJECT + Q_PROPERTY(QString platform READ getPlatformName CONSTANT) Q_PROPERTY(QString buildDate READ getBuildDate CONSTANT) Q_PROPERTY(QString buildVersion READ getBuildVersion CONSTANT) Q_PROPERTY(QString qtVersion READ getQtVersion CONSTANT) @@ -65,6 +71,7 @@ public: static AboutUtil* getInstance(); ~AboutUtil() {} + QString getPlatformName() const { return "Vircadia"; } QString getBuildDate() const; QString getBuildVersion() const; QString getQtVersion() const; From d4cbc35bc630abbdc40f74818e6a4e45d2c488cc Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sun, 16 Aug 2020 21:51:44 -0400 Subject: [PATCH 054/388] Rollback, since we will use external storage Rollback, since we will use external storage. --- interface/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index e5a2e01e33..608bbb19cc 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -351,9 +351,6 @@ if (APPLE) COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" - COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/resources/default" - "${RESOURCES_DEV_DIR}/default" #copy serverless for android COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/serverless" @@ -384,9 +381,6 @@ else() COMMAND "${CMAKE_COMMAND}" -E copy_directory "${PROJECT_SOURCE_DIR}/resources/fonts" "${RESOURCES_DEV_DIR}/fonts" - COMMAND "${CMAKE_COMMAND}" -E copy_directory - "${PROJECT_SOURCE_DIR}/resources/default" - "${RESOURCES_DEV_DIR}/default" COMMAND "${CMAKE_COMMAND}" -E copy_directory "${CMAKE_SOURCE_DIR}/scripts" "${INTERFACE_EXEC_DIR}/scripts" From 341c114adf6f0925b8630db8874728ea36f04b6c Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sun, 16 Aug 2020 21:58:35 -0400 Subject: [PATCH 055/388] Replace default url for external storage var DEFAULT_IMAGE = "http://eu-central-1.linodeobjects.com/vircadia-assets/interface/default/default_image.jpg"; var DEFAULT_PARTICLE = "http://eu-central-1.linodeobjects.com/vircadia-assets/interface/default/default_particle.png"; --- scripts/system/create/edit.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js index d34bd42188..0de87e563c 100644 --- a/scripts/system/create/edit.js +++ b/scripts/system/create/edit.js @@ -44,7 +44,8 @@ var CREATE_TOOLS_WIDTH = 490; var MAX_DEFAULT_ENTITY_LIST_HEIGHT = 942; var ENTIRE_DOMAIN_SCAN_RADIUS = 27713; -var DEFAULT_IMAGE = "file:///~/default/default_image.jpg"; +var DEFAULT_IMAGE = "http://eu-central-1.linodeobjects.com/vircadia-assets/interface/default/default_image.jpg"; +var DEFAULT_PARTICLE = "http://eu-central-1.linodeobjects.com/vircadia-assets/interface/default/default_particle.png"; var createToolsWindow = new CreateWindow( Script.resolvePath("qml/EditTools.qml"), @@ -436,7 +437,7 @@ const DEFAULT_ENTITY_PROPERTIES = { ParticleEffect: { lifespan: 1.5, maxParticles: 10, - textures: "file:///~/default/default_particle.png", + textures: DEFAULT_PARTICLE, emitRate: 5.5, emitSpeed: 0, speedSpread: 0, From 40e1e8e59c5c0d208abf6a8c4caf3618143f9cbb Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sun, 16 Aug 2020 22:00:04 -0400 Subject: [PATCH 056/388] Delete default_image.jpg --- interface/resources/default/default_image.jpg | Bin 156659 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 interface/resources/default/default_image.jpg diff --git a/interface/resources/default/default_image.jpg b/interface/resources/default/default_image.jpg deleted file mode 100644 index 830af928baa56f616a0ff2d10adfff9a3712528f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 156659 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 +qUb_6bSDyDtYvvXr%_;>@j3uU2X&syfp< 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 z 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 G&;JkUbGWwv From e1d956b211be9a90a3f22fc86ae5243f11c7622f Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Sun, 16 Aug 2020 22:00:22 -0400 Subject: [PATCH 057/388] Delete default_particle.png --- .../resources/default/default_particle.png | Bin 79835 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 interface/resources/default/default_particle.png diff --git a/interface/resources/default/default_particle.png b/interface/resources/default/default_particle.png deleted file mode 100644 index 6c781c824befad6e069574c51eecc309f06047d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 79835 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 i>pI-HapR?2DE|*TvSYzVE)fF&0000 Date: Mon, 17 Aug 2020 02:44:26 -0400 Subject: [PATCH 058/388] Centralizing some more networking constants. --- interface/src/avatar/MyAvatar.cpp | 3 ++- interface/src/ui/ModelsBrowser.cpp | 4 ++-- libraries/entities/src/WebEntityItem.cpp | 3 ++- libraries/networking/src/NetworkingConstants.h | 8 ++++++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 570ac5fce2..27af9b3c14 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -50,6 +50,7 @@ #include #include #include +#include #include "MyHead.h" #include "MySkeletonModel.h" @@ -82,7 +83,7 @@ const int SCRIPTED_MOTOR_AVATAR_FRAME = 1; const int SCRIPTED_MOTOR_WORLD_FRAME = 2; const int SCRIPTED_MOTOR_SIMPLE_MODE = 0; const int SCRIPTED_MOTOR_DYNAMIC_MODE = 1; -const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav"; +const QString& DEFAULT_AVATAR_COLLISION_SOUND_URL = NetworkingConstants::DEFAULT_AVATAR_COLLISION_SOUND_URL; const float MyAvatar::ZOOM_MIN = 0.5f; const float MyAvatar::ZOOM_MAX = 25.0f; diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp index 96c10be212..cccfb6e6d6 100644 --- a/interface/src/ui/ModelsBrowser.cpp +++ b/interface/src/ui/ModelsBrowser.cpp @@ -32,8 +32,8 @@ const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "skeletons", "attachments" }; -static const QString S3_URL = "http://s3.amazonaws.com/hifi-public"; -static const QString PUBLIC_URL = "http://public.highfidelity.io"; +static const QString S3_URL = NetworkingConstants::S3_URL; +static const QString PUBLIC_URL = NetworkingConstants::PUBLIC_URL; static const QString MODELS_LOCATION = "models/"; static const QString PREFIX_PARAMETER_NAME = "prefix"; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 57b53bd911..5419dca53c 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -17,13 +17,14 @@ #include #include #include +#include #include "EntitiesLogging.h" #include "EntityItemProperties.h" #include "EntityTree.h" #include "EntityTreeElement.h" -const QString WebEntityItem::DEFAULT_SOURCE_URL = "https://www.vircadia.com"; +const QString WebEntityItem::DEFAULT_SOURCE_URL = NetworkingConstants::WEB_ENTITY_DEFAULT_SOURCE_URL; const uint8_t WebEntityItem::DEFAULT_MAX_FPS = 10; const QString WebEntityItem::DEFAULT_WEB_BACKGROUND_COLOR = "#FFFFFFFF"; diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index edc1c1a1ef..0e490885cd 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -41,7 +41,15 @@ namespace NetworkingConstants { const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml"); + + // WebEntity Defaults + const QString WEB_ENTITY_DEFAULT_SOURCE_URL = "https://vircadia.com/"; + + const QString DEFAULT_AVATAR_COLLISION_SOUND_URL = "https://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/Body_Hits_Impact.wav"; + // CDN URLs + const QString S3_URL = "http://s3.amazonaws.com/hifi-public"; + const QString PUBLIC_URL = "http://public.highfidelity.io"; #if USE_STABLE_GLOBAL_SERVICES const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com"; From 7e5e513dc1584e382f5d5e53857564f58f4b0ea6 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Mon, 17 Aug 2020 15:13:07 -0400 Subject: [PATCH 059/388] Update Web3DSurface.qml --- interface/resources/qml/Web3DSurface.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 3a3a2ef8b1..7ac7c2593b 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -53,7 +53,7 @@ Item { root.item = newItem root.item.url = url root.item.scriptUrl = scriptUrl - root.item.transparentBackground = webBackgroundColor.endsWith("FF") ? false : true + root.item.transparentBackground = webBackgroundColor.startsWith("#FF") ? false : true }) } From e78ece7fc5e64ec225807924d39ebe070d72e3c6 Mon Sep 17 00:00:00 2001 From: HifiExperiments Date: Mon, 17 Aug 2020 14:16:41 -0700 Subject: [PATCH 060/388] CR --- libraries/entities-renderer/src/EntityTreeRenderer.cpp | 2 +- libraries/render-utils/src/Model.cpp | 4 ++-- libraries/render-utils/src/Model.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 51178fea1b..b5ed4b767d 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -496,7 +496,7 @@ void EntityTreeRenderer::updateChangedEntities(const render::ScenePointer& scene } // compute average per-renderable update cost - _prevNumEntityUpdates = sortedRenderables.size() - _renderablesToUpdate.size(); + _prevNumEntityUpdates = sortedRenderables.size() - _renderablesToUpdate.size(); size_t numUpdated = _prevNumEntityUpdates + 1; // add one to avoid divide by zero float cost = (float)(usecTimestampNow() - updateStart) / (float)(numUpdated); const float BLEND = 0.1f; diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp index 92c9b4fafb..182b83762c 100644 --- a/libraries/render-utils/src/Model.cpp +++ b/libraries/render-utils/src/Model.cpp @@ -958,10 +958,10 @@ void Model::setCauterized(bool cauterized, const render::ScenePointer& scene) { } } -void Model::setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene) { +void Model::setPrimitiveMode(PrimitiveMode primitiveMode) { if (_primitiveMode != primitiveMode) { _primitiveMode = primitiveMode; - updateRenderItemsKey(scene); + updateRenderItemsKey(nullptr); } } diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 0f65b62194..4585ad0009 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -118,7 +118,7 @@ public: bool isCauterized() const { return _cauterized; } void setCauterized(bool value, const render::ScenePointer& scene); - void setPrimitiveMode(PrimitiveMode primitiveMode, const render::ScenePointer& scene = nullptr); + void setPrimitiveMode(PrimitiveMode primitiveMode); PrimitiveMode getPrimitiveMode() const { return _primitiveMode; } void setCullWithParent(bool value); From 9bb5add1ca8e1bf290c378930da660f8efe51ca4 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Aug 2020 11:42:35 +1200 Subject: [PATCH 061/388] Don't display domain login status in title bar unless relevant --- interface/src/Application.cpp | 26 ++++--- .../networking/src/DomainAccountManager.cpp | 73 ++++++++++++++----- .../networking/src/DomainAccountManager.h | 15 +++- libraries/networking/src/DomainHandler.cpp | 20 ++++- libraries/networking/src/NodeList.cpp | 21 ++---- 5 files changed, 108 insertions(+), 47 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index cc2aed7f53..f97c48fbdf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1352,10 +1352,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); auto domainAccountManager = DependencyManager::get(); - connect(domainAccountManager.data(), &DomainAccountManager::authRequired, dialogsManager.data(), - &DialogsManager::showDomainLoginDialog); - connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, - &Application::updateWindowTitle); + connect(domainAccountManager.data(), &DomainAccountManager::authRequired, + dialogsManager.data(), &DialogsManager::showDomainLoginDialog); + connect(domainAccountManager.data(), &DomainAccountManager::authRequired, this, &Application::updateWindowTitle); + connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, &Application::updateWindowTitle); // ####### TODO: Connect any other signals from domainAccountManager. // use our MyAvatar position and quat for address manager path @@ -7084,6 +7084,7 @@ void Application::updateWindowTitle() const { auto domainAccountManager = DependencyManager::get(); auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); bool isMetaverseLoggedIn = accountManager->isLoggedIn(); + bool hasDomainLogIn = domainAccountManager->hasLogIn(); bool isDomainLoggedIn = domainAccountManager->isLoggedIn(); QString authedDomain = domainAccountManager->getAuthedDomain(); @@ -7115,20 +7116,23 @@ void Application::updateWindowTitle() const { QString metaverseDetails; if (isMetaverseLoggedIn) { - metaverseDetails = "Metaverse: Logged in as " + metaverseUsername; + metaverseDetails = " (Metaverse: Logged in as " + metaverseUsername + ")"; } else { - metaverseDetails = "Metaverse: Not Logged In"; + metaverseDetails = " (Metaverse: Not Logged In)"; } QString domainDetails; - if (currentPlaceName == authedDomain && isDomainLoggedIn) { - domainDetails = "Domain: Logged in as " + domainUsername; + if (hasDomainLogIn) { + if (currentPlaceName == authedDomain && isDomainLoggedIn) { + domainDetails = " (Domain: Logged in as " + domainUsername + ")"; + } else { + domainDetails = " (Domain: Not Logged In)"; + } } else { - domainDetails = "Domain: Not Logged In"; + domainDetails = ""; } - QString title = QString() + currentPlaceName + connectionStatus + " (" + metaverseDetails + ") (" + domainDetails + ")" - + buildVersion; + QString title = currentPlaceName + connectionStatus + metaverseDetails + domainDetails + buildVersion; #ifndef WIN32 // crashes with vs2013/win32 diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 928b581a5b..6475d50cbb 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -39,29 +39,54 @@ Setting::Handle domainAccessTokenExpiresIn {"private/domainAccessTokenExpir Setting::Handle domainAccessTokenType {"private/domainAccessTokenType", "" }; */ -DomainAccountManager::DomainAccountManager() : - _authURL(), - _username(), - _access_token(), - _refresh_token(), - _domain_name() -{ +DomainAccountManager::DomainAccountManager() { connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer); } -void DomainAccountManager::setAuthURL(const QUrl& authURL) { - if (_authURL != authURL) { - _authURL = authURL; +void DomainAccountManager::setDomainURL(const QUrl& domainURL) { - qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); - - _access_token = ""; - _refresh_token = ""; - - // ####### TODO: Restore and refresh OAuth2 tokens if have them for this domain. - - // ####### TODO: Handle "keep me logged in". + if (domainURL == _domainURL) { + return; } + + _domainURL = domainURL; + qCDebug(networking) << "DomainAccountManager domain URL has been changed to" << qPrintable(_domainURL.toString()); + + // Restore OAuth2 authorization if have it for this domain. + if (_domainURL == _previousDomainURL) { + _authURL = _previousAuthURL; + _clientID = _previousClientID; + _username = _previousUsername; + _access_token = _previousAccessToken; + _refresh_token = _previousRefreshToken; + _domain_name = _previousDomainName; + // ####### TODO: Refresh OAuth2 access token if necessary. + } else { + _authURL.clear(); + _clientID.clear(); + _username.clear(); + _access_token.clear(); + _refresh_token.clear(); + _domain_name.clear(); + } + +} + +void DomainAccountManager::setAuthURL(const QUrl& authURL) { + if (_authURL == authURL) { + return; + } + + _authURL = authURL; + qCDebug(networking) << "DomainAccountManager URL for authenticated requests has been changed to" + << qPrintable(_authURL.toString()); + + _access_token = ""; + _refresh_token = ""; +} + +bool DomainAccountManager::hasLogIn() { + return !_authURL.isEmpty(); } bool DomainAccountManager::isLoggedIn() { @@ -115,6 +140,18 @@ void DomainAccountManager::requestAccessTokenFinished() { QUrl rootURL = requestReply->url(); rootURL.setPath(""); setTokensFromJSON(rootObject, rootURL); + + // Remember domain login for the current Interface session. + _previousDomainURL = _domainURL; + _previousAuthURL = _authURL; + _previousClientID = _clientID; + _previousUsername = _username; + _previousAccessToken = _access_token; + _previousRefreshToken = _refresh_token; + _previousDomainName = _domain_name; + + // ####### TODO: Handle "keep me logged in". + emit loginComplete(); } else { // Failure. diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 31226d6990..765217549d 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -23,6 +23,7 @@ class DomainAccountManager : public QObject, public Dependency { public: DomainAccountManager(); + void setDomainURL(const QUrl& domainURL); void setAuthURL(const QUrl& authURL); void setClientID(const QString& clientID) { _clientID = clientID; } @@ -31,6 +32,7 @@ public: QString getRefreshToken() { return _refresh_token; } QString getAuthedDomain() { return _domain_name; } + bool hasLogIn(); bool isLoggedIn(); Q_INVOKABLE bool checkAndSignalForAccessToken(); @@ -55,12 +57,23 @@ private: void setTokensFromJSON(const QJsonObject&, const QUrl& url); void sendInterfaceAccessTokenToServer(); + QUrl _domainURL; QUrl _authURL; QString _clientID; - QString _username; // ####### TODO: Store elsewhere? + + QString _username; QString _access_token; // ####... "" QString _refresh_token; // ####... "" QString _domain_name; // ####... "" + + // ####### TODO: Handle more than one domain. + QUrl _previousDomainURL; + QUrl _previousAuthURL; + QString _previousClientID; + QString _previousUsername; + QString _previousAccessToken; + QString _previousRefreshToken; + QString _previousDomainName; }; #endif // hifi_DomainAccountManager_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp index c1a24748f4..ea4c0a00cb 100644 --- a/libraries/networking/src/DomainHandler.cpp +++ b/libraries/networking/src/DomainHandler.cpp @@ -241,6 +241,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) { } } + DependencyManager::get()->setDomainURL(_domainURL); + emit domainURLChanged(_domainURL); if (_sockAddr.getPort() != domainPort) { @@ -584,18 +586,28 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointergenerateNewUserKeypair(); _connectionDenialsSinceKeypairRegen = 0; } + + // Server with domain login will prompt for domain login, not metaverse, so reset domain values if asked for metaverse. + auto domainAccountManager = DependencyManager::get(); + domainAccountManager->setAuthURL(QUrl()); + domainAccountManager->setClientID(QString()); + } else if (reasonSuggestsDomainLogin(reasonCode)) { qCWarning(networking) << "Make sure you are logged in to the domain."; - auto accountManager = DependencyManager::get(); + auto domainAccountManager = DependencyManager::get(); if (!extraInfo.isEmpty()) { auto extraInfoComponents = extraInfo.split("|"); - accountManager->setAuthURL(extraInfoComponents.value(0)); - accountManager->setClientID(extraInfoComponents.value(1)); + domainAccountManager->setAuthURL(extraInfoComponents.value(0)); + domainAccountManager->setClientID(extraInfoComponents.value(1)); + } else { + // Shouldn't occur, but just in case. + domainAccountManager->setAuthURL(QUrl()); + domainAccountManager->setClientID(QString()); } if (!_hasCheckedForDomainAccessToken) { - accountManager->checkAndSignalForAccessToken(); + domainAccountManager->checkAndSignalForAccessToken(); _hasCheckedForDomainAccessToken = true; } diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index e02a8dd56e..64663def22 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -476,33 +476,28 @@ void NodeList::sendDomainServerCheckIn() { packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList(); packetStream << DependencyManager::get()->getPlaceName(); - // ####### TODO: Also send if need to send new domainLogin data? if (!domainIsConnected) { + + // Metaverse account. DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); packetStream << accountInfo.getUsername(); - // if this is a connect request, and we can present a username signature, send it along if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) { const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken); packetStream << usernameSignature; } else { - // ####### TODO: Only append if are going to send domain username? packetStream << QString(""); // Placeholder in case have domain username. } - } else { - // ####### TODO: Only append if are going to send domainUsername? - packetStream << QString("") << QString(""); // Placeholders in case have domain username. - } - // Send domain domain login data from Interface to domain server. - if (_hasDomainAccountManager) { - auto domainAccountManager = DependencyManager::get(); - if (!domainAccountManager->getUsername().isEmpty()) { - packetStream << domainAccountManager->getUsername(); - if (!domainAccountManager->getAccessToken().isEmpty()) { + // Domain account. + if (_hasDomainAccountManager) { + auto domainAccountManager = DependencyManager::get(); + if (!domainAccountManager->getUsername().isEmpty() && !domainAccountManager->getAccessToken().isEmpty()) { + packetStream << domainAccountManager->getUsername(); packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken()); } } + } flagTimeForConnectionStep(LimitedNodeList::ConnectionStep::SendDSCheckIn); From 8d217d215fe23a11390666326b1c8f3eeeb032a4 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Mon, 17 Aug 2020 21:58:42 -0400 Subject: [PATCH 062/388] Better code fix for preview being more specific on the validation for the unresolvable relative path. checking now if the path start with "file:///~" instead of only: "file:/" This was too much restrictive since those direct path works and wouldn't return a preview: Ex: "file://MYSERVER/folder/100_0057.jpg" --- .../system/create/entityProperties/html/js/entityProperties.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index 0b0a3a286a..f6dcf10bc8 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -2890,7 +2890,7 @@ function createTextureProperty(property, elProperty) { let imageLoad = function(url) { elDiv.style.display = null; - if (url.slice(0, 5).toLowerCase() === "atp:/" || url.slice(0, 6).toLowerCase() === "file:/") { + if (url.slice(0, 5).toLowerCase() === "atp:/" || url.slice(0, 9).toLowerCase() === "file:///~") { elImage.src = ""; elImage.style.display = "none"; elDiv.classList.remove("with-texture"); From bf60cf5b4c1780dc0d51409dd25d2a106b2d5f68 Mon Sep 17 00:00:00 2001 From: David Rowe Date: Tue, 18 Aug 2020 16:33:47 +1200 Subject: [PATCH 063/388] Tidying --- interface/src/Application.cpp | 4 +-- .../networking/src/DomainAccountManager.cpp | 36 ++++++++++--------- .../networking/src/DomainAccountManager.h | 17 +++++---- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index f97c48fbdf..c0eb8072cc 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -7086,7 +7086,7 @@ void Application::updateWindowTitle() const { bool isMetaverseLoggedIn = accountManager->isLoggedIn(); bool hasDomainLogIn = domainAccountManager->hasLogIn(); bool isDomainLoggedIn = domainAccountManager->isLoggedIn(); - QString authedDomain = domainAccountManager->getAuthedDomain(); + QString authedDomainName = domainAccountManager->getAuthedDomainName(); QString buildVersion = " - Vircadia - " + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) @@ -7123,7 +7123,7 @@ void Application::updateWindowTitle() const { QString domainDetails; if (hasDomainLogIn) { - if (currentPlaceName == authedDomain && isDomainLoggedIn) { + if (currentPlaceName == authedDomainName && isDomainLoggedIn) { domainDetails = " (Domain: Logged in as " + domainUsername + ")"; } else { domainDetails = " (Domain: Not Logged In)"; diff --git a/libraries/networking/src/DomainAccountManager.cpp b/libraries/networking/src/DomainAccountManager.cpp index 6475d50cbb..04baacd560 100644 --- a/libraries/networking/src/DomainAccountManager.cpp +++ b/libraries/networking/src/DomainAccountManager.cpp @@ -57,17 +57,19 @@ void DomainAccountManager::setDomainURL(const QUrl& domainURL) { _authURL = _previousAuthURL; _clientID = _previousClientID; _username = _previousUsername; - _access_token = _previousAccessToken; - _refresh_token = _previousRefreshToken; - _domain_name = _previousDomainName; + _accessToken = _previousAccessToken; + _refreshToken = _previousRefreshToken; + _authedDomainName = _previousAuthedDomainName; + // ####### TODO: Refresh OAuth2 access token if necessary. + } else { _authURL.clear(); _clientID.clear(); _username.clear(); - _access_token.clear(); - _refresh_token.clear(); - _domain_name.clear(); + _accessToken.clear(); + _refreshToken.clear(); + _authedDomainName.clear(); } } @@ -81,8 +83,8 @@ void DomainAccountManager::setAuthURL(const QUrl& authURL) { qCDebug(networking) << "DomainAccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString()); - _access_token = ""; - _refresh_token = ""; + _accessToken = ""; + _refreshToken = ""; } bool DomainAccountManager::hasLogIn() { @@ -96,8 +98,8 @@ bool DomainAccountManager::isLoggedIn() { void DomainAccountManager::requestAccessToken(const QString& username, const QString& password) { _username = username; - _access_token = ""; - _refresh_token = ""; + _accessToken = ""; + _refreshToken = ""; QNetworkRequest request; @@ -136,7 +138,7 @@ void DomainAccountManager::requestAccessTokenFinished() { if (rootObject.contains("access_token")) { // Success. auto nodeList = DependencyManager::get(); - _domain_name = nodeList->getDomainHandler().getHostname(); + _authedDomainName = nodeList->getDomainHandler().getHostname(); QUrl rootURL = requestReply->url(); rootURL.setPath(""); setTokensFromJSON(rootObject, rootURL); @@ -146,9 +148,9 @@ void DomainAccountManager::requestAccessTokenFinished() { _previousAuthURL = _authURL; _previousClientID = _clientID; _previousUsername = _username; - _previousAccessToken = _access_token; - _previousRefreshToken = _refresh_token; - _previousDomainName = _domain_name; + _previousAccessToken = _accessToken; + _previousRefreshToken = _refreshToken; + _previousAuthedDomainName = _authedDomainName; // ####### TODO: Handle "keep me logged in". @@ -183,7 +185,7 @@ bool DomainAccountManager::accessTokenIsExpired() { bool DomainAccountManager::hasValidAccessToken() { // ###### TODO: wire this up to actually retrieve a token (based on session or storage) and confirm that it is in fact valid and relevant to the current domain. // QString currentDomainAccessToken = domainAccessToken.get(); - QString currentDomainAccessToken = _access_token; + QString currentDomainAccessToken = _accessToken; // if (currentDomainAccessToken.isEmpty() || accessTokenIsExpired()) { if (currentDomainAccessToken.isEmpty()) { @@ -205,8 +207,8 @@ bool DomainAccountManager::hasValidAccessToken() { } void DomainAccountManager::setTokensFromJSON(const QJsonObject& jsonObject, const QUrl& url) { - _access_token = jsonObject["access_token"].toString(); - _refresh_token = jsonObject["refresh_token"].toString(); + _accessToken = jsonObject["access_token"].toString(); + _refreshToken = jsonObject["refresh_token"].toString(); // ####### TODO: Enable and use these? // ####### TODO: Protect these per AccountManager? diff --git a/libraries/networking/src/DomainAccountManager.h b/libraries/networking/src/DomainAccountManager.h index 765217549d..b27c9ce0bf 100644 --- a/libraries/networking/src/DomainAccountManager.h +++ b/libraries/networking/src/DomainAccountManager.h @@ -27,10 +27,10 @@ public: void setAuthURL(const QUrl& authURL); void setClientID(const QString& clientID) { _clientID = clientID; } - QString getUsername() { return _username; } - QString getAccessToken() { return _access_token; } - QString getRefreshToken() { return _refresh_token; } - QString getAuthedDomain() { return _domain_name; } + const QString& getUsername() { return _username; } + const QString& getAccessToken() { return _accessToken; } + const QString& getRefreshToken() { return _refreshToken; } + const QString& getAuthedDomainName() { return _authedDomainName; } bool hasLogIn(); bool isLoggedIn(); @@ -39,7 +39,6 @@ public: public slots: void requestAccessToken(const QString& username, const QString& password); - void requestAccessTokenFinished(); signals: @@ -62,9 +61,9 @@ private: QString _clientID; QString _username; - QString _access_token; // ####... "" - QString _refresh_token; // ####... "" - QString _domain_name; // ####... "" + QString _accessToken; + QString _refreshToken; + QString _authedDomainName; // ####### TODO: Handle more than one domain. QUrl _previousDomainURL; @@ -73,7 +72,7 @@ private: QString _previousUsername; QString _previousAccessToken; QString _previousRefreshToken; - QString _previousDomainName; + QString _previousAuthedDomainName; }; #endif // hifi_DomainAccountManager_h From b05cb8b6ac7ea95909c23478c425c250005266a0 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 18 Aug 2020 01:48:27 -0400 Subject: [PATCH 064/388] useBackground property added. --- interface/resources/qml/Web3DSurface.qml | 18 ++++++++-------- .../+webengine/FlickableWebViewCore.qml | 7 ++----- .../qml/controls/FlickableWebViewCore.qml | 3 +-- interface/resources/qml/controls/WebView.qml | 2 +- .../src/RenderableWebEntityItem.cpp | 14 +++++++------ .../src/RenderableWebEntityItem.h | 2 +- .../entities/src/EntityItemProperties.cpp | 20 +++++++++--------- libraries/entities/src/EntityItemProperties.h | 2 +- libraries/entities/src/EntityPropertyFlags.h | 2 +- libraries/entities/src/WebEntityItem.cpp | 21 +++++++++---------- libraries/entities/src/WebEntityItem.h | 9 ++++---- 11 files changed, 48 insertions(+), 52 deletions(-) diff --git a/interface/resources/qml/Web3DSurface.qml b/interface/resources/qml/Web3DSurface.qml index 7ac7c2593b..e9c8d119d6 100644 --- a/interface/resources/qml/Web3DSurface.qml +++ b/interface/resources/qml/Web3DSurface.qml @@ -18,31 +18,31 @@ Item { anchors.fill: parent property string url: "" property string scriptUrl: null - property string webBackgroundColor: "#FFFFFFFF" + property bool useBackground: true onUrlChanged: { - load(root.url, root.scriptUrl, root.webBackgroundColor); + load(root.url, root.scriptUrl, root.useBackground); } onScriptUrlChanged: { if (root.item) { root.item.scriptUrl = root.scriptUrl; } else { - load(root.url, root.scriptUrl, root.webBackgroundColor); + load(root.url, root.scriptUrl, root.useBackground); } } - onWebBackgroundColorChanged: { + onUseBackgroundChanged: { if (root.item) { - root.item.webBackgroundColor = root.webBackgroundColor; + root.item.useBackground = root.useBackground; } else { - load(root.url, root.scriptUrl, root.webBackgroundColor); + load(root.url, root.scriptUrl, root.useBackground); } } property var item: null - function load(url, scriptUrl, webBackgroundColor) { + function load(url, scriptUrl, useBackground) { // Ensure we reset any existing item to "about:blank" to ensure web audio stops: DEV-2375 if (root.item != null) { root.item.url = "about:blank" @@ -53,12 +53,12 @@ Item { root.item = newItem root.item.url = url root.item.scriptUrl = scriptUrl - root.item.transparentBackground = webBackgroundColor.startsWith("#FF") ? false : true + root.item.useBackground = useBackground }) } Component.onCompleted: { - load(root.url, root.scriptUrl, root.webBackgroundColor); + load(root.url, root.scriptUrl, root.useBackground); } signal sendToScript(var message); diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index 93126152a2..d7d84a0961 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -15,8 +15,8 @@ Item { property alias webViewCore: webViewCore property alias webViewCoreProfile: webViewCore.profile property string webViewCoreUserAgent - property string webBackgroundColor: "#FFFFFFFF" // Fully opaque white. + property bool useBackground: true property string userScriptUrl: "" property string urlTag: "noDownload=false"; @@ -99,10 +99,7 @@ Item { width: parent.width height: parent.height - //backgroundColor: "transparent" - //backgroundColor: "#FFFF00CC" - backgroundColor: flick.webBackgroundColor - //backgroundColor: Qt.rgba(0.502, 0.502, 0.502, 0.502) + backgroundColor: (flick.useBackground) ? "white" : "transparent" profile: HFWebEngineProfile; settings.pluginsEnabled: true diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index cccd42457a..4f611a4d9b 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -14,8 +14,7 @@ Item { property alias webViewCoreProfile: webViewCore.profile property string webViewCoreUserAgent - property string webBackgroundColor: "#FFFFFFFF" - + property bool useBackground: true property string userScriptUrl: "" property string urlTag: "noDownload=false"; diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index 663bbf7867..b46c8c904d 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -23,7 +23,7 @@ Item { property bool passwordField: false property alias flickable: webroot.interactive property alias blurOnCtrlShift: webroot.blurOnCtrlShift - property alias webBackgroundColor: webroot.webBackgroundColor + property alias useBackground: webroot.useBackground function stop() { webroot.stop(); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index b3d1edcfb5..dc48a575f1 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -101,7 +101,7 @@ WebEntityRenderer::~WebEntityRenderer() { bool WebEntityRenderer::isTransparent() const { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; - return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE; + return fadeRatio < OPAQUE_ALPHA_THRESHOLD || _alpha < 1.0f || _pulseProperties.getAlphaMode() != PulseMode::NONE || !_useBackground; } bool WebEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPointer& entity) const { @@ -228,10 +228,10 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene } { - auto webBackgroundColor = entity->getWebBackgroundColor(); - if (_webBackgroundColor != webBackgroundColor) { - _webSurface->getRootItem()->setProperty("webBackgroundColor", webBackgroundColor); - _webBackgroundColor = webBackgroundColor; + auto useBackground = entity->getUseBackground(); + if (_useBackground != useBackground) { + _webSurface->getRootItem()->setProperty("useBackground", useBackground); + _useBackground = useBackground; } } @@ -300,12 +300,14 @@ void WebEntityRenderer::doRender(RenderArgs* args) { glm::vec4 color; Transform transform; bool forward; + bool isTransparent; withReadLock([&] { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; color = glm::vec4(toGlm(_color), _alpha * fadeRatio); color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); transform = _renderTransform; forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; + isTransparent = isTransparent(); }); if (color.a == 0.0f) { @@ -319,7 +321,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { // Turn off jitter for these entities batch.pushProjectionJitter(); - DependencyManager::get()->bindWebBrowserProgram(batch, color.a < OPAQUE_ALPHA_THRESHOLD, forward); + DependencyManager::get()->bindWebBrowserProgram(batch, isTransparent, forward); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId); batch.popProjectionJitter(); batch.setResourceTexture(0, nullptr); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 786df55ee5..b03ea93ef5 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -94,7 +94,7 @@ private: uint16_t _dpi; QString _scriptURL; uint8_t _maxFPS; - QString _webBackgroundColor; + bool _useBackground; WebInputMode _inputMode; glm::vec3 _contextPosition; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 294ac6228e..921904ed25 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -602,7 +602,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const { CHECK_PROPERTY_CHANGE(PROP_MAX_FPS, maxFPS); CHECK_PROPERTY_CHANGE(PROP_INPUT_MODE, inputMode); CHECK_PROPERTY_CHANGE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); - CHECK_PROPERTY_CHANGE(PROP_WEB_BACKGROUND_COLOR, webBackgroundColor); + CHECK_PROPERTY_CHANGE(PROP_WEB_USE_BACKGROUND, useBackground); // Polyline CHECK_PROPERTY_CHANGE(PROP_LINE_POINTS, linePoints); @@ -1821,7 +1821,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MAX_FPS, maxFPS); COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(PROP_INPUT_MODE, inputMode, getInputModeAsString()); COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, showKeyboardFocusHighlight); - COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_WEB_BACKGROUND_COLOR, webBackgroundColor); + COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_WEB_USE_BACKGROUND, useBackground); } // PolyVoxel only @@ -2203,7 +2203,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool COPY_PROPERTY_FROM_QSCRIPTVALUE(maxFPS, uint8_t, setMaxFPS); COPY_PROPERTY_FROM_QSCRIPTVALUE_ENUM(inputMode, InputMode); COPY_PROPERTY_FROM_QSCRIPTVALUE(showKeyboardFocusHighlight, bool, setShowKeyboardFocusHighlight); - COPY_PROPERTY_FROM_QSCRIPTVALUE(webBackgroundColor, QString, setWebBackgroundColor); + COPY_PROPERTY_FROM_QSCRIPTVALUE(useBackground, bool, setUseBackground); // Polyline COPY_PROPERTY_FROM_QSCRIPTVALUE(linePoints, qVectorVec3, setLinePoints); @@ -2495,7 +2495,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) { COPY_PROPERTY_IF_CHANGED(maxFPS); COPY_PROPERTY_IF_CHANGED(inputMode); COPY_PROPERTY_IF_CHANGED(showKeyboardFocusHighlight); - COPY_PROPERTY_IF_CHANGED(webBackgroundColor); + COPY_PROPERTY_IF_CHANGED(useBackground); // Polyline COPY_PROPERTY_IF_CHANGED(linePoints); @@ -2895,7 +2895,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr ADD_PROPERTY_TO_MAP(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t); ADD_PROPERTY_TO_MAP(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode); ADD_PROPERTY_TO_MAP(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool); - ADD_PROPERTY_TO_MAP(PROP_WEB_BACKGROUND_COLOR, WebBackgroundColor, webBackgroundColor, QString); + ADD_PROPERTY_TO_MAP(PROP_WEB_USE_BACKGROUND, useBackground, useBackground, bool); // Polyline ADD_PROPERTY_TO_MAP(PROP_LINE_POINTS, LinePoints, linePoints, QVector); @@ -3326,7 +3326,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, properties.getMaxFPS()); APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)properties.getInputMode()); APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, properties.getShowKeyboardFocusHighlight()); - APPEND_ENTITY_PROPERTY(PROP_WEB_BACKGROUND_COLOR, properties.getWebBackgroundColor()); + APPEND_ENTITY_PROPERTY(PROP_WEB_USE_BACKGROUND, properties.getUseBackground()); } if (properties.getType() == EntityTypes::Line) { @@ -3802,7 +3802,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MAX_FPS, uint8_t, setMaxFPS); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_INPUT_MODE, WebInputMode, setInputMode); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_WEB_BACKGROUND_COLOR, QString, setWebBackgroundColor); + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_WEB_USE_BACKGROUND, bool, setUseBackground); } if (properties.getType() == EntityTypes::Line) { @@ -4190,7 +4190,7 @@ void EntityItemProperties::markAllChanged() { _maxFPSChanged = true; _inputModeChanged = true; _showKeyboardFocusHighlightChanged = true; - _webBackgroundColor = true; + _useBackgroundChanged = true; // Polyline _linePointsChanged = true; @@ -4881,8 +4881,8 @@ QList EntityItemProperties::listChangedProperties() { if (showKeyboardFocusHighlightChanged()) { out += "showKeyboardFocusHighlight"; } - if (webBackgroundColorChanged()) { - out += "webBackgroundColor"; + if (useBackgroundChanged()) { + out += "useBackground"; } // Shape diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6049391ba6..f7fde73430 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -366,7 +366,7 @@ public: DEFINE_PROPERTY_REF(PROP_MAX_FPS, MaxFPS, maxFPS, uint8_t, WebEntityItem::DEFAULT_MAX_FPS); DEFINE_PROPERTY_REF_ENUM(PROP_INPUT_MODE, InputMode, inputMode, WebInputMode, WebInputMode::TOUCH); DEFINE_PROPERTY_REF(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, ShowKeyboardFocusHighlight, showKeyboardFocusHighlight, bool, true); - DEFINE_PROPERTY_REF(PROP_WEB_BACKGROUND_COLOR, WebBackgroundColor, webBackgroundColor, QString, WebEntityItem::DEFAULT_WEB_BACKGROUND_COLOR); + DEFINE_PROPERTY_REF(PROP_WEB_USE_BACKGROUND, UseBackground, useBackground, bool, true); // Polyline DEFINE_PROPERTY_REF(PROP_LINE_POINTS, LinePoints, linePoints, QVector, ENTITY_ITEM_DEFAULT_EMPTY_VEC3_QVEC); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index 0e068544e3..93bb8a89a7 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -318,7 +318,7 @@ enum EntityPropertyList { PROP_MAX_FPS = PROP_DERIVED_3, PROP_INPUT_MODE = PROP_DERIVED_4, PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT = PROP_DERIVED_5, - PROP_WEB_BACKGROUND_COLOR = PROP_DERIVED_6, + PROP_WEB_USE_BACKGROUND = PROP_DERIVED_6, // Polyline PROP_LINE_POINTS = PROP_DERIVED_0, diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 5419dca53c..99daca47f4 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -26,7 +26,6 @@ const QString WebEntityItem::DEFAULT_SOURCE_URL = NetworkingConstants::WEB_ENTITY_DEFAULT_SOURCE_URL; const uint8_t WebEntityItem::DEFAULT_MAX_FPS = 10; -const QString WebEntityItem::DEFAULT_WEB_BACKGROUND_COLOR = "#FFFFFFFF"; EntityItemPointer WebEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer entity(new WebEntityItem(entityID), [](EntityItem* ptr) { ptr->deleteLater(); }); @@ -63,7 +62,7 @@ EntityItemProperties WebEntityItem::getProperties(const EntityPropertyFlags& des COPY_ENTITY_PROPERTY_TO_PROPERTIES(maxFPS, getMaxFPS); COPY_ENTITY_PROPERTY_TO_PROPERTIES(inputMode, getInputMode); COPY_ENTITY_PROPERTY_TO_PROPERTIES(showKeyboardFocusHighlight, getShowKeyboardFocusHighlight); - COPY_ENTITY_PROPERTY_TO_PROPERTIES(webBackgroundColor, getWebBackgroundColor); + COPY_ENTITY_PROPERTY_TO_PROPERTIES(useBackground, getUseBackground); return properties; } @@ -86,7 +85,7 @@ bool WebEntityItem::setProperties(const EntityItemProperties& properties) { SET_ENTITY_PROPERTY_FROM_PROPERTIES(maxFPS, setMaxFPS); SET_ENTITY_PROPERTY_FROM_PROPERTIES(inputMode, setInputMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(showKeyboardFocusHighlight, setShowKeyboardFocusHighlight); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(webBackgroundColor, setWebBackgroundColor); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(useBackground, setUseBackground); if (somethingChanged) { bool wantDebug = false; @@ -127,7 +126,7 @@ int WebEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, i READ_ENTITY_PROPERTY(PROP_MAX_FPS, uint8_t, setMaxFPS); READ_ENTITY_PROPERTY(PROP_INPUT_MODE, WebInputMode, setInputMode); READ_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, bool, setShowKeyboardFocusHighlight); - READ_ENTITY_PROPERTY(PROP_WEB_BACKGROUND_COLOR, QString, setWebBackgroundColor); + READ_ENTITY_PROPERTY(PROP_WEB_USE_BACKGROUND, bool, setUseBackground); return bytesRead; } @@ -145,7 +144,7 @@ EntityPropertyFlags WebEntityItem::getEntityProperties(EncodeBitstreamParams& pa requestedProperties += PROP_MAX_FPS; requestedProperties += PROP_INPUT_MODE; requestedProperties += PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT; - requestedProperties += PROP_WEB_BACKGROUND_COLOR; + requestedProperties += PROP_WEB_USE_BACKGROUND; return requestedProperties; } @@ -172,7 +171,7 @@ void WebEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBitst APPEND_ENTITY_PROPERTY(PROP_MAX_FPS, getMaxFPS()); APPEND_ENTITY_PROPERTY(PROP_INPUT_MODE, (uint32_t)getInputMode()); APPEND_ENTITY_PROPERTY(PROP_SHOW_KEYBOARD_FOCUS_HIGHLIGHT, getShowKeyboardFocusHighlight()); - APPEND_ENTITY_PROPERTY(PROP_WEB_BACKGROUND_COLOR, getWebBackgroundColor()); + APPEND_ENTITY_PROPERTY(PROP_WEB_USE_BACKGROUND, getUseBackground()); } glm::vec3 WebEntityItem::getRaycastDimensions() const { @@ -367,15 +366,15 @@ bool WebEntityItem::getShowKeyboardFocusHighlight() const { return _showKeyboardFocusHighlight; } -void WebEntityItem::setWebBackgroundColor(const QString& value) { +void WebEntityItem::setUseBackground(bool value) { withWriteLock([&] { - _needsRenderUpdate |= _webBackgroundColor != value; - _webBackgroundColor = value; + _needsRenderUpdate |= _useBackground != value; + _useBackground = value; }); } -QString WebEntityItem::getWebBackgroundColor() const { - return resultWithReadLock([&] { return _webBackgroundColor; }); +bool WebEntityItem::getUseBackground() const { + return resultWithReadLock([&] { return _useBackground; }); } PulsePropertyGroup WebEntityItem::getPulseProperties() const { diff --git a/libraries/entities/src/WebEntityItem.h b/libraries/entities/src/WebEntityItem.h index 25a0a162e5..8b92cf3bb6 100644 --- a/libraries/entities/src/WebEntityItem.h +++ b/libraries/entities/src/WebEntityItem.h @@ -81,15 +81,14 @@ public: void setMaxFPS(uint8_t value); uint8_t getMaxFPS() const; - static const QString DEFAULT_WEB_BACKGROUND_COLOR; - void setWebBackgroundColor(const QString& value); - QString getWebBackgroundColor() const; - void setInputMode(const WebInputMode& value); WebInputMode getInputMode() const; bool getShowKeyboardFocusHighlight() const; void setShowKeyboardFocusHighlight(bool value); + + bool getUseBackground() const; + void setUseBackground(bool value); PulsePropertyGroup getPulseProperties() const; @@ -103,9 +102,9 @@ protected: uint16_t _dpi; QString _scriptURL; uint8_t _maxFPS; - QString _webBackgroundColor; WebInputMode _inputMode; bool _showKeyboardFocusHighlight; + bool _useBackground; bool _localSafeContext { false }; }; From fe1ea6ca724eda56d8eb395f5c2fd4e24bbd9023 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 18 Aug 2020 02:22:56 -0400 Subject: [PATCH 065/388] Bump protocol. --- libraries/networking/src/udt/PacketHeaders.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index a34be09db7..9738e7e704 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -281,6 +281,7 @@ enum class EntityVersion : PacketVersion { ScreenshareZone, ZoneOcclusion, ModelBlendshapes, + TransparentWeb, // Add new versions above here NUM_PACKET_TYPE, From c379f6c92f3d84c171ff7dd33a6545946242df96 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 18 Aug 2020 03:21:34 -0400 Subject: [PATCH 066/388] Fix transparency check bug. Co-Authored-By: null --- .../entities-renderer/src/RenderableWebEntityItem.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index dc48a575f1..8606cbb049 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -175,6 +175,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _alpha = entity->getAlpha(); _pulseProperties = entity->getPulseProperties(); _billboardMode = entity->getBillboardMode(); + _useBackground = entity->getUseBackground(); if (_contentType == ContentType::NoContent) { _tryingToBuildURL = newSourceURL; @@ -300,14 +301,14 @@ void WebEntityRenderer::doRender(RenderArgs* args) { glm::vec4 color; Transform transform; bool forward; - bool isTransparent; + bool isTransparentWeb; withReadLock([&] { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; color = glm::vec4(toGlm(_color), _alpha * fadeRatio); color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); transform = _renderTransform; forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; - isTransparent = isTransparent(); + isTransparentWeb = isTransparent(); }); if (color.a == 0.0f) { @@ -321,7 +322,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { // Turn off jitter for these entities batch.pushProjectionJitter(); - DependencyManager::get()->bindWebBrowserProgram(batch, isTransparent, forward); + DependencyManager::get()->bindWebBrowserProgram(batch, isTransparentWeb, forward); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId); batch.popProjectionJitter(); batch.setResourceTexture(0, nullptr); From 8d904d39ce13f6e44f40badff1005582fdfa7a3b Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 18 Aug 2020 04:17:29 -0400 Subject: [PATCH 067/388] Semi-working fixes for updates to web entities. Co-Authored-By: null --- .../src/RenderableWebEntityItem.cpp | 19 +++++++++++++++---- .../src/RenderableWebEntityItem.h | 4 ++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 8606cbb049..d441941175 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -36,6 +36,10 @@ using namespace render::entities; const QString WebEntityRenderer::QML = "Web3DSurface.qml"; const char* WebEntityRenderer::URL_PROPERTY = "url"; +const char* WebEntityRenderer::MAX_FPS_PROPERTY = "maxFPS"; +const char* WebEntityRenderer::SCRIPT_URL_PROPERTY = "scriptURL"; +const char* WebEntityRenderer::GLOBAL_POSITION_PROPERTY = "globalPosition"; +const char* WebEntityRenderer::USE_BACKGROUND_PROPERTY = "useBackground"; std::function&, bool&)> WebEntityRenderer::_acquireWebSurfaceOperator = nullptr; std::function&, bool&, std::vector&)> WebEntityRenderer::_releaseWebSurfaceOperator = nullptr; @@ -175,7 +179,10 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _alpha = entity->getAlpha(); _pulseProperties = entity->getPulseProperties(); _billboardMode = entity->getBillboardMode(); - _useBackground = entity->getUseBackground(); + // _maxFPS = entity->getMaxFPS(); + // _scriptURL = entity->getScriptURL(); + // _contextPosition = entity->getWorldPosition(); + // _useBackground = entity->getUseBackground(); if (_contentType == ContentType::NoContent) { _tryingToBuildURL = newSourceURL; @@ -200,6 +207,10 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene ::hifi::scripting::setLocalAccessSafeThread(true); } _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); + _webSurface->getRootItem()->setProperty(MAX_FPS_PROPERTY, _maxFPS); + _webSurface->getRootItem()->setProperty(SCRIPT_URL_PROPERTY, _scriptURL); + _webSurface->getRootItem()->setProperty(USE_BACKGROUND_PROPERTY, _useBackground); + _webSurface->getSurfaceContext()->setContextProperty(GLOBAL_POSITION_PROPERTY, vec3toVariant(_contextPosition)); ::hifi::scripting::setLocalAccessSafeThread(false); _sourceURL = newSourceURL; } else if (_contentType != ContentType::HtmlContent) { @@ -209,7 +220,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene { auto scriptURL = entity->getScriptURL(); if (_scriptURL != scriptURL) { - _webSurface->getRootItem()->setProperty("scriptURL", scriptURL); + _webSurface->getRootItem()->setProperty(SCRIPT_URL_PROPERTY, scriptURL); _scriptURL = scriptURL; } } @@ -231,7 +242,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene { auto useBackground = entity->getUseBackground(); if (_useBackground != useBackground) { - _webSurface->getRootItem()->setProperty("useBackground", useBackground); + _webSurface->getRootItem()->setProperty(USE_BACKGROUND_PROPERTY, useBackground); _useBackground = useBackground; } } @@ -239,7 +250,7 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene { auto contextPosition = entity->getWorldPosition(); if (_contextPosition != contextPosition) { - _webSurface->getSurfaceContext()->setContextProperty("globalPosition", vec3toVariant(contextPosition)); + _webSurface->getSurfaceContext()->setContextProperty(GLOBAL_POSITION_PROPERTY, vec3toVariant(contextPosition)); _contextPosition = contextPosition; } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index b03ea93ef5..0db69851cc 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -33,6 +33,10 @@ public: static const QString QML; static const char* URL_PROPERTY; + static const char* MAX_FPS_PROPERTY; + static const char* SCRIPT_URL_PROPERTY; + static const char* GLOBAL_POSITION_PROPERTY; + static const char* USE_BACKGROUND_PROPERTY; static void setAcquireWebSurfaceOperator(std::function&, bool&)> acquireWebSurfaceOperator) { _acquireWebSurfaceOperator = acquireWebSurfaceOperator; } static void acquireWebSurface(const QString& url, bool htmlContent, QSharedPointer& webSurface, bool& cachedWebSurface) { From 0e609b677711ff2559bb7d3262678cd3246920b5 Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 18 Aug 2020 18:49:28 -0400 Subject: [PATCH 068/388] Fixed web entities useBackground on reload. Co-Authored-By: null --- .../resources/qml/controls/+webengine/FlickableWebViewCore.qml | 2 +- interface/resources/qml/controls/FlickableWebViewCore.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml index d7d84a0961..a0585ae053 100644 --- a/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/+webengine/FlickableWebViewCore.qml @@ -16,7 +16,7 @@ Item { property alias webViewCoreProfile: webViewCore.profile property string webViewCoreUserAgent - property bool useBackground: true + property bool useBackground: webViewCore.useBackground property string userScriptUrl: "" property string urlTag: "noDownload=false"; diff --git a/interface/resources/qml/controls/FlickableWebViewCore.qml b/interface/resources/qml/controls/FlickableWebViewCore.qml index 4f611a4d9b..35f3182f98 100644 --- a/interface/resources/qml/controls/FlickableWebViewCore.qml +++ b/interface/resources/qml/controls/FlickableWebViewCore.qml @@ -14,7 +14,7 @@ Item { property alias webViewCoreProfile: webViewCore.profile property string webViewCoreUserAgent - property bool useBackground: true + property bool useBackground: webViewCore.useBackground property string userScriptUrl: "" property string urlTag: "noDownload=false"; From 204789bd421c273d8b0748f7bb03aec114f8dfbc Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 18 Aug 2020 19:22:54 -0400 Subject: [PATCH 069/388] Further fix up web entity property processing on reload. Co-Authored-By: null --- .../entities-renderer/src/RenderableWebEntityItem.cpp | 9 ++------- .../entities-renderer/src/RenderableWebEntityItem.h | 1 - 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index d441941175..4f6beb5e90 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -36,7 +36,6 @@ using namespace render::entities; const QString WebEntityRenderer::QML = "Web3DSurface.qml"; const char* WebEntityRenderer::URL_PROPERTY = "url"; -const char* WebEntityRenderer::MAX_FPS_PROPERTY = "maxFPS"; const char* WebEntityRenderer::SCRIPT_URL_PROPERTY = "scriptURL"; const char* WebEntityRenderer::GLOBAL_POSITION_PROPERTY = "globalPosition"; const char* WebEntityRenderer::USE_BACKGROUND_PROPERTY = "useBackground"; @@ -179,10 +178,6 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene _alpha = entity->getAlpha(); _pulseProperties = entity->getPulseProperties(); _billboardMode = entity->getBillboardMode(); - // _maxFPS = entity->getMaxFPS(); - // _scriptURL = entity->getScriptURL(); - // _contextPosition = entity->getWorldPosition(); - // _useBackground = entity->getUseBackground(); if (_contentType == ContentType::NoContent) { _tryingToBuildURL = newSourceURL; @@ -202,15 +197,15 @@ void WebEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene if (_webSurface) { if (_webSurface->getRootItem()) { - if (_contentType == ContentType::HtmlContent && _sourceURL != newSourceURL) { + if (_contentType == ContentType::HtmlContent && _sourceURL != newSourceURL) { if (localSafeContext) { ::hifi::scripting::setLocalAccessSafeThread(true); } _webSurface->getRootItem()->setProperty(URL_PROPERTY, newSourceURL); - _webSurface->getRootItem()->setProperty(MAX_FPS_PROPERTY, _maxFPS); _webSurface->getRootItem()->setProperty(SCRIPT_URL_PROPERTY, _scriptURL); _webSurface->getRootItem()->setProperty(USE_BACKGROUND_PROPERTY, _useBackground); _webSurface->getSurfaceContext()->setContextProperty(GLOBAL_POSITION_PROPERTY, vec3toVariant(_contextPosition)); + _webSurface->setMaxFps((QUrl(newSourceURL).host().endsWith("youtube.com", Qt::CaseInsensitive)) ? YOUTUBE_MAX_FPS : _maxFPS); ::hifi::scripting::setLocalAccessSafeThread(false); _sourceURL = newSourceURL; } else if (_contentType != ContentType::HtmlContent) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 0db69851cc..ffd5880c1e 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -33,7 +33,6 @@ public: static const QString QML; static const char* URL_PROPERTY; - static const char* MAX_FPS_PROPERTY; static const char* SCRIPT_URL_PROPERTY; static const char* GLOBAL_POSITION_PROPERTY; static const char* USE_BACKGROUND_PROPERTY; From 103288b8de82b2049d60bec8ee4fb36a8de653eb Mon Sep 17 00:00:00 2001 From: Kasen IO Date: Tue, 18 Aug 2020 20:04:27 -0400 Subject: [PATCH 070/388] Update bool name "isTransparentWeb" -> "transparent" --- libraries/entities-renderer/src/RenderableWebEntityItem.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 4f6beb5e90..6117065df6 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -307,14 +307,14 @@ void WebEntityRenderer::doRender(RenderArgs* args) { glm::vec4 color; Transform transform; bool forward; - bool isTransparentWeb; + bool transparent; withReadLock([&] { float fadeRatio = _isFading ? Interpolate::calculateFadeRatio(_fadeStartTime) : 1.0f; color = glm::vec4(toGlm(_color), _alpha * fadeRatio); color = EntityRenderer::calculatePulseColor(color, _pulseProperties, _created); transform = _renderTransform; forward = _renderLayer != RenderLayer::WORLD || args->_renderMethod == render::Args::FORWARD; - isTransparentWeb = isTransparent(); + transparent = isTransparent(); }); if (color.a == 0.0f) { @@ -328,7 +328,7 @@ void WebEntityRenderer::doRender(RenderArgs* args) { // Turn off jitter for these entities batch.pushProjectionJitter(); - DependencyManager::get()->bindWebBrowserProgram(batch, isTransparentWeb, forward); + DependencyManager::get()->bindWebBrowserProgram(batch, transparent, forward); DependencyManager::get()->renderQuad(batch, topLeft, bottomRight, texMin, texMax, color, _geometryId); batch.popProjectionJitter(); batch.setResourceTexture(0, nullptr); From bda88828a0f907eed02cefe42b15442d1dcaf7c1 Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Tue, 18 Aug 2020 23:21:32 -0400 Subject: [PATCH 071/388] Tooltip for useBackground Tooltip for useBackground --- .../create/assets/data/createAppTooltips.json | 1331 +++++++++-------- 1 file changed, 667 insertions(+), 664 deletions(-) diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json index 4efd0593fb..7c0ca4f6fa 100644 --- a/scripts/system/create/assets/data/createAppTooltips.json +++ b/scripts/system/create/assets/data/createAppTooltips.json @@ -1,664 +1,667 @@ -{ - "shape": { - "tooltip": "The shape of this entity's geometry." - }, - "color": { - "tooltip": "The color of this entity." - }, - "shapeAlpha": { - "tooltip": "The opacity of the entity between 0.0 fully transparent and 1.0 completely opaque." - }, - "text": { - "tooltip": "The text to display on the entity." - }, - "textColor": { - "tooltip": "The color of the text." - }, - "textAlpha": { - "tooltip": "The opacity of the text between 0.0 fully transparent and 1.0 completely opaque." - }, - "backgroundColor": { - "tooltip": "The color of the background." - }, - "backgroundAlpha": { - "tooltip": "The opacity of the background between 0.0 fully transparent and 1.0 completely opaque." - }, - "lineHeight": { - "tooltip": "The height of each line of text. This determines the size of the text." - }, - "font": { - "tooltip": "The font to render the text. Supported values: \"Courier\", \"Inconsolata\", \"Roboto\", \"Timeless\", or a URL to a .sdff file." - }, - "textEffect": { - "tooltip": "The effect that is applied to the text." - }, - "textEffectColor": { - "tooltip": "The color of the text effect." - }, - "textEffectThickness": { - "tooltip": "The magnitude of the text effect." - }, - "textBillboardMode": { - "tooltip": "If enabled, determines how the entity will face the camera.", - "jsPropertyName": "billboardMode" - }, - "topMargin": { - "tooltip": "The top margin, in meters." - }, - "rightMargin": { - "tooltip": "The right margin, in meters." - }, - "bottomMargin": { - "tooltip": "The bottom margin, in meters." - }, - "leftMargin": { - "tooltip": "The left margin, in meters." - }, - "unlit": { - "tooltip": "If enabled, the entity will not be lit by the keylight or local lights.", - "jsPropertyName": "unlit" - }, - "zoneShapeType": { - "tooltip": "The shape of the volume in which the zone's lighting effects and avatar permissions have effect.", - "jsPropertyName": "shapeType" - }, - "zoneCompoundShapeURL": { - "tooltip": "The model file to use for the compound shape if Shape Type is \"Use Compound Shape URL\".", - "jsPropertyName": "compoundShapeURL" - }, - "flyingAllowed": { - "tooltip": "If enabled, users can fly in the zone." - }, - "ghostingAllowed": { - "tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone." - }, - "filterURL": { - "tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically." - }, - "keyLightMode": { - "tooltip": "Configures the key light in the zone. This light is directional." - }, - "keyLight.color": { - "tooltip": "The color of the key light." - }, - "keyLight.intensity": { - "tooltip": "The intensity of the key light." - }, - "keyLight.direction.y": { - "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis." - }, - "keyLight.direction.x": { - "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis." - }, - "keyLight.castShadows": { - "tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled. Note: Shadows are rendered only on high-profiled computers. This setting will have no effect on computers profiled to medium or low graphics." - }, - "keyLight.shadowBias": { - "tooltip": "The bias of the shadows cast by the light. Use this to fine-tune your shadows to your scene to prevent shadow acne and peter panning." - }, - "keyLight.shadowMaxDistance": { - "tooltip": "The max distance from your view at which shadows will be computed." - }, - "skyboxMode": { - "tooltip": "Configures the skybox in the zone. The skybox is a cube map image." - }, - "skybox.color": { - "tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox." - }, - "skybox.url": { - "tooltip": "A cube map image that is used to render the sky." - }, - "ambientLightMode": { - "tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content." - }, - "ambientLight.ambientIntensity": { - "tooltip": "The intensity of the ambient light." - }, - "ambientLight.ambientURL": { - "tooltip": "A cube map image that defines the color of the light coming from each direction." - }, - "hazeMode": { - "tooltip": "Configures the haze in the scene." - }, - "haze.hazeRange": { - "tooltip": "How far the haze extends out. This is measured in meters." - }, - "haze.hazeAltitudeEffect": { - "tooltip": "If enabled, this adjusts the haze intensity as it gets higher." - }, - "haze.hazeBaseRef": { - "tooltip": "The base of the altitude range. Measured in entity space." - }, - "haze.hazeCeiling": { - "tooltip": "The ceiling of the altitude range. Measured in entity space." - }, - "haze.hazeColor": { - "tooltip": "The color of the haze." - }, - "haze.hazeBackgroundBlend": { - "tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through." - }, - "haze.hazeEnableGlare": { - "tooltip": "If enabled, a glare is enabled on the skybox, based on the key light." - }, - "haze.hazeGlareColor": { - "tooltip": "The color of the glare based on the key light." - }, - "haze.hazeGlareAngle": { - "tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light." - }, - "bloomMode": { - "tooltip": "Configures how much bright areas of the scene glow." - }, - "bloom.bloomIntensity": { - "tooltip": "The intensity, or brightness, of the bloom effect." - }, - "bloom.bloomThreshold": { - "tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow." - }, - "bloom.bloomSize": { - "tooltip": "The radius of bloom. The higher the value, the larger the bloom." - }, - "avatarPriority": { - "tooltip": "Alter Avatars' update priorities." - }, - "screenshare": { - "tooltip": "Enable screen-sharing within this zone" - }, - "modelURL": { - "tooltip": "A mesh model from an FBX or OBJ file." - }, - "shapeType": { - "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." - }, - "compoundShapeURL": { - "tooltip": "The model file to use for the compound shape if Collision Shape is \"Compound\"." - }, - "animation.url": { - "tooltip": "An animation to play on the model." - }, - "animation.running": { - "tooltip": "If enabled, the animation on the model will play automatically." - }, - "animation.allowTranslation": { - "tooltip": "If enabled, this allows an entity to move in space during an animation." - }, - "animation.loop": { - "tooltip": "If enabled, then the animation will continuously repeat." - }, - "animation.hold": { - "tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops." - }, - "animation.currentFrame": { - "tooltip": "The current frame being played in the animation." - }, - "animation.firstFrame": { - "tooltip": "The first frame to play in the animation." - }, - "animation.lastFrame": { - "tooltip": "The last frame to play in the animation." - }, - "animation.fps": { - "tooltip": "The speed of the animation." - }, - "textures": { - "tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it." - }, - "originalTextures": { - "tooltip": "A JSON string containing the original texture used on the model." - }, - "imageURL": { - "tooltip": "The URL for the image source." - }, - "imageColor": { - "tooltip": "The tint to be applied to the image.", - "jsPropertyName": "color" - }, - "imageAlpha": { - "tooltip": "The opacity of the image between 0.0 fully transparent and 1.0 completely opaque." - }, - "emissive": { - "tooltip": "If enabled, the image will display at full brightness." - }, - "subImage": { - "tooltip": "The area of the image that is displayed." - }, - "imageBillboardMode": { - "tooltip": "If enabled, determines how the entity will face the camera.", - "jsPropertyName": "billboardMode" - }, - "keepAspectRatio": { - "tooltip": "If enabled, the image will maintain its original aspect ratio." - }, - "sourceUrl": { - "tooltip": "The URL for the web page source." - }, - "dpi": { - "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." - }, - "webBillboardMode": { - "tooltip": "If enabled, determines how the entity will face the camera.", - "jsPropertyName": "billboardMode" - }, - "inputMode": { - "tooltip": "The user input mode to use." - }, - "showKeyboardFocusHighlight": { - "tooltip": "If enabled, highlights when it has keyboard focus." - }, - "isEmitting": { - "tooltip": "If enabled, then particles are emitted." - }, - "lifespan": { - "tooltip": "How long each particle lives, measured in seconds." - }, - "maxParticles": { - "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." - }, - "particleTextures": { - "tooltip": "The URL of a JPG or PNG image file to display for each particle.", - "jsPropertyName": "textures" - }, - "emitRate": { - "tooltip": "The number of particles per second to emit." - }, - "emitSpeed": { - "tooltip": "The speed that each particle is emitted at, measured in m/s." - }, - "speedSpread": { - "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." - }, - "particleShapeType": { - "tooltip": "The shape of the surface from which to emit particles.", - "jsPropertyName": "shapeType" - }, - "particleCompoundShapeURL": { - "tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".", - "jsPropertyName": "compoundShapeURL" - }, - "emitDimensions": { - "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." - }, - "emitOrientation": { - "tooltip": "The orientation of particle emission relative to the entity's axes." - }, - "emitRadiusStart": { - "tooltip": "The inner limit radius in dimensions that the particles start emitting from." - }, - "emitterShouldTrail": { - "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." - }, - "particleRadiusTriple": { - "tooltip": "The size of each particle.", - "jsPropertyName": "particleRadius" - }, - "particleRadius": { - "tooltip": "The size of each particle." - }, - "radiusStart": { - "tooltip": "The start size of each particle." - }, - "radiusFinish": { - "tooltip": "The finish size of each particle." - }, - "radiusSpread": { - "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." - }, - "particleColorTriple": { - "tooltip": "The color of each particle.", - "jsPropertyName": "color" - }, - "particleColor": { - "tooltip": "The color of each particle.", - "jsPropertyName": "color" - }, - "colorStart": { - "tooltip": "The start color of each particle." - }, - "colorFinish": { - "tooltip": "The finish color of each particle." - }, - "colorSpread": { - "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." - }, - "particleAlphaTriple": { - "tooltip": "The opacity of each particle between 0.0 fully transparent and 1.0 completely opaque.", - "jsPropertyName": "alpha" - }, - "alpha": { - "tooltip": "The opacity of each particle between 0.0 fully transparent and 1.0 completely opaque." - }, - "alphaStart": { - "tooltip": "The initial opacity level of each particle between 0.0 fully transparent and 1.0 completely opaque." - }, - "alphaFinish": { - "tooltip": "The final opacity level of each particle between 0.0 fully transparent and 1.0 completely opaque." - }, - "alphaSpread": { - "tooltip": "The spread in opacity that each particle is given, resulting in a variety of opacity levels." - }, - "emitAcceleration": { - "tooltip": "The acceleration that is applied to each particle during its lifetime." - }, - "accelerationSpread": { - "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." - }, - "particleSpinTriple": { - "tooltip": "The spin of each particle.", - "jsPropertyName": "particleSpin" - }, - "particleSpin": { - "tooltip": "The spin of each particle." - }, - "spinStart": { - "tooltip": "The start spin of each particle." - }, - "spinFinish": { - "tooltip": "The finish spin of each particle." - }, - "spinSpread": { - "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." - }, - "rotateWithEntity": { - "tooltip": "If enabled, each particle will spin relative to the rotation of the entity as a whole." - }, - "particlePolarTriple": { - "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis.", - "skipJSProperty": true - }, - "polarStart": { - "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." - }, - "polarFinish": { - "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." - }, - "particleAzimuthTriple": { - "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis.", - "skipJSProperty": true - }, - "azimuthStart": { - "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." - }, - "azimuthFinish": { - "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." - }, - "lightColor": { - "tooltip": "The color of the light emitted.", - "jsPropertyName": "color" - }, - "intensity": { - "tooltip": "The brightness of the light." - }, - "falloffRadius": { - "tooltip": "The distance from the light's center where the intensity is reduced." - }, - "isSpotlight": { - "tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions." - }, - "exponent": { - "tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam." - }, - "cutoff": { - "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." - }, - "materialURL": { - "tooltip": "The URL to an external JSON file or \"materialData\", \"materialData? to use Material Data." - }, - "materialData": { - "tooltip": "Can be used instead of a JSON file when material set to materialData." - }, - "parentMaterialName": { - "tooltip": "The target mesh indices or material names that this material entity should be assigned to on it's parent. This only supports parents that are Avatars as well as Shape or Model entity types." - }, - "priority": { - "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." - }, - "materialMappingMode": { - "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entity's UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." - }, - "materialMappingPos": { - "tooltip": "The offset position of the bottom left of the material within the parent's UV space." - }, - "materialMappingScale": { - "tooltip": "How many times the material will repeat in each direction within the parent's UV space." - }, - "materialMappingRot": { - "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." - }, - "materialRepeat": { - "tooltip": "If enabled, the material will repeat, otherwise it will clamp." - }, - "followCamera": { - "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." - }, - "majorGridEvery": { - "tooltip": "The number of \"Minor Grid Every\" intervals at which to draw a thick grid line." - }, - "minorGridEvery": { - "tooltip": "The real number of meters at which to draw thin grid lines." - }, - "id": { - "tooltip": "The unique identifier of this entity." - }, - "name": { - "tooltip": "The name of this entity." - }, - "description": { - "tooltip": "Use this field to describe the entity." - }, - "position": { - "tooltip": "The global position of this entity." - }, - "localPosition": { - "tooltip": "The local position of this entity." - }, - "rotation": { - "tooltip": "The global rotation of this entity." - }, - "localRotation": { - "tooltip": "The local rotation of this entity." - }, - "dimensions": { - "tooltip": "The global dimensions of this entity." - }, - "localDimensions": { - "tooltip": "The local dimensions of this entity." - }, - "scale": { - "tooltip": "The global scaling of this entity.", - "skipJSProperty": true - }, - "registrationPoint": { - "tooltip": "The point in the entity at which the entity is rotated about." - }, - "visible": { - "tooltip": "If enabled, this entity will be visible." - }, - "locked": { - "tooltip": "If enabled, this entity will be locked." - }, - "collisionless": { - "tooltip": "If enabled, this entity will collide with other entities or avatars." - }, - "dynamic": { - "tooltip": "If enabled, this entity has collisions associated with it that can affect its movement." - }, - "collidesWithStatic": { - "tooltip": "If enabled, this entity will collide with other non-moving, static entities.", - "jsPropertyName": "collidesWith" - }, - "collidesWithDynamic": { - "tooltip": "If enabled, this entity will collide with other dynamic entities.", - "jsPropertyName": "collidesWith" - }, - "collidesWithKinematic": { - "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", - "jsPropertyName": "collidesWith" - }, - "collidesWithOtherAvatar": { - "tooltip": "If enabled, this entity will collide with other user's avatars.", - "jsPropertyName": "collidesWith" - }, - "collidesWithMyAvatar": { - "tooltip": "If enabled, this entity will collide with your own avatar.", - "jsPropertyName": "collidesWith" - }, - "collisionSoundURL": { - "tooltip": "The URL of a sound to play when the entity collides with something else." - }, - "grab.grabbable": { - "tooltip": "If enabled, this entity will allow grabbing input and will be movable." - }, - "grab.triggerable": { - "tooltip": "If enabled, the collider on this entity is used for triggering events." - }, - "cloneable": { - "tooltip": "If enabled, this entity can be duplicated." - }, - "cloneLifetime": { - "tooltip": "The lifetime for clones of this entity." - }, - "cloneLimit": { - "tooltip": "The total number of clones of this entity that can exist in the domain at any given time." - }, - "cloneDynamic": { - "tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide." - }, - "cloneAvatarEntity": { - "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." - }, - "grab.grabFollowsController": { - "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." - }, - "canCastShadow": { - "tooltip": "If enabled, the geometry of this entity casts shadows when a shadow-casting light source shines on it. Note: Shadows are rendered only on high-profiled computers. This setting will have no effect on computers profiled to medium or low graphics.." - }, - "ignorePickIntersection": { - "tooltip": "If enabled, this entity will not be considered for ray picks, and will also not occlude other entities when picking." - }, - "parentID": { - "tooltip": "The ID of the entity or avatar that this entity is parented to." - }, - "parentJointIndex": { - "tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented." - }, - "href": { - "tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals." - }, - "script": { - "tooltip": "The URL to an external JS file to add behaviors to the client." - }, - "serverScripts": { - "tooltip": "The URL to an external JS file to add behaviors to the server." - }, - "serverScriptsStatus": { - "tooltip": "The status of the server script, if provided. This shows if it's running or has an error.", - "skipJSProperty": true - }, - "hasLifetime": { - "tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.", - "jsPropertyName": "lifetime" - }, - "lifetime": { - "tooltip": "The time this entity will exist in the environment for." - }, - "userData": { - "tooltip": "Used to store extra data about the entity in JSON format." - }, - "localVelocity": { - "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." - }, - "damping": { - "tooltip": "The linear damping to slow down the linear velocity of an entity over time." - }, - "localAngularVelocity": { - "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." - }, - "angularDamping": { - "tooltip": "The angular damping to slow down the angular velocity of an entity over time." - }, - "restitution": { - "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." - }, - "friction": { - "tooltip": "The friction applied to slow down an entity when it's moving against another entity." - }, - "density": { - "tooltip": "The density of the entity. The higher the density, the harder the entity is to move." - }, - "gravity": { - "tooltip": "The acceleration due to gravity that the entity should move with, in world space." - }, - "renderLayer": { - "tooltip": "The layer on which this entity is rendered." - }, - "primitiveMode": { - "tooltip": "The mode in which to draw an entity, either \"Solid\" or \"Wireframe\"." - }, - "renderWithZones": { - "tooltip": "If set, this entity will only render when your avatar is inside of a zone in this list." - }, - "groupCulled": { - "tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all." - }, - "webColor": { - "tooltip": "The tint of the web entity." - }, - "webAlpha": { - "tooltip": "The opacity of the web entity between 0.0 fully transparent and 1.0 completely opaque." - }, - "maxFPS": { - "tooltip": "The FPS at which to render the web entity. Higher values will have a performance impact." - }, - "scriptURL": { - "tooltip": "The URL of a script to inject into the web page." - }, - "alignToGrid": { - "tooltip": "Used to align entities to the grid, or floor of the environment.", - "skipJSProperty": true - }, - "createModel": { - "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", - "skipJSProperty": true - }, - "createShape": { - "tooltip": "An entity that has many different primitive shapes.", - "skipJSProperty": true - }, - "createLight": { - "tooltip": "An entity that emits light.", - "skipJSProperty": true - }, - "createText": { - "tooltip": "An entity that displays text on a panel.", - "skipJSProperty": true - }, - "createImage": { - "tooltip": "An entity that displays an image on a panel.", - "skipJSProperty": true - }, - "createWeb": { - "tooltip": "An entity that displays a web page on a panel.", - "skipJSProperty": true - }, - "createZone": { - "tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.", - "skipJSProperty": true - }, - "createParticle": { - "tooltip": "An entity that emits particles.", - "skipJSProperty": true - }, - "createMaterial": { - "tooltip": "An entity that creates a material that can be attached to a Shape or Model.", - "skipJSProperty": true - }, - "useAssetServer": { - "tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.", - "skipJSProperty": true - }, - "importNewEntity": { - "tooltip": "Import a local or hosted file that can be used across domains.", - "skipJSProperty": true - } -} +{ + "shape": { + "tooltip": "The shape of this entity's geometry." + }, + "color": { + "tooltip": "The color of this entity." + }, + "shapeAlpha": { + "tooltip": "The opacity of the entity between 0.0 fully transparent and 1.0 completely opaque." + }, + "text": { + "tooltip": "The text to display on the entity." + }, + "textColor": { + "tooltip": "The color of the text." + }, + "textAlpha": { + "tooltip": "The opacity of the text between 0.0 fully transparent and 1.0 completely opaque." + }, + "backgroundColor": { + "tooltip": "The color of the background." + }, + "backgroundAlpha": { + "tooltip": "The opacity of the background between 0.0 fully transparent and 1.0 completely opaque." + }, + "lineHeight": { + "tooltip": "The height of each line of text. This determines the size of the text." + }, + "font": { + "tooltip": "The font to render the text. Supported values: \"Courier\", \"Inconsolata\", \"Roboto\", \"Timeless\", or a URL to a .sdff file." + }, + "textEffect": { + "tooltip": "The effect that is applied to the text." + }, + "textEffectColor": { + "tooltip": "The color of the text effect." + }, + "textEffectThickness": { + "tooltip": "The magnitude of the text effect." + }, + "textBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "topMargin": { + "tooltip": "The top margin, in meters." + }, + "rightMargin": { + "tooltip": "The right margin, in meters." + }, + "bottomMargin": { + "tooltip": "The bottom margin, in meters." + }, + "leftMargin": { + "tooltip": "The left margin, in meters." + }, + "unlit": { + "tooltip": "If enabled, the entity will not be lit by the keylight or local lights.", + "jsPropertyName": "unlit" + }, + "zoneShapeType": { + "tooltip": "The shape of the volume in which the zone's lighting effects and avatar permissions have effect.", + "jsPropertyName": "shapeType" + }, + "zoneCompoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, + "flyingAllowed": { + "tooltip": "If enabled, users can fly in the zone." + }, + "ghostingAllowed": { + "tooltip": "If enabled, users with avatar collisions turned off will not collide with content in the zone." + }, + "filterURL": { + "tooltip": "The URL of a JS file that checks for changes to entity properties within the zone. Runs periodically." + }, + "keyLightMode": { + "tooltip": "Configures the key light in the zone. This light is directional." + }, + "keyLight.color": { + "tooltip": "The color of the key light." + }, + "keyLight.intensity": { + "tooltip": "The intensity of the key light." + }, + "keyLight.direction.y": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its y axis." + }, + "keyLight.direction.x": { + "tooltip": "The angle in deg at which light emits. Starts in the entity's -z direction, and rotates around its x axis." + }, + "keyLight.castShadows": { + "tooltip": "If enabled, shadows are cast. The entity or avatar casting the shadow must also have Cast Shadows enabled. Note: Shadows are rendered only on high-profiled computers. This setting will have no effect on computers profiled to medium or low graphics." + }, + "keyLight.shadowBias": { + "tooltip": "The bias of the shadows cast by the light. Use this to fine-tune your shadows to your scene to prevent shadow acne and peter panning." + }, + "keyLight.shadowMaxDistance": { + "tooltip": "The max distance from your view at which shadows will be computed." + }, + "skyboxMode": { + "tooltip": "Configures the skybox in the zone. The skybox is a cube map image." + }, + "skybox.color": { + "tooltip": "If the URL is blank, this changes the color of the sky, otherwise it modifies the color of the skybox." + }, + "skybox.url": { + "tooltip": "A cube map image that is used to render the sky." + }, + "ambientLightMode": { + "tooltip": "Configures the ambient light in the zone. Use this if you want your skybox to reflect light on the content." + }, + "ambientLight.ambientIntensity": { + "tooltip": "The intensity of the ambient light." + }, + "ambientLight.ambientURL": { + "tooltip": "A cube map image that defines the color of the light coming from each direction." + }, + "hazeMode": { + "tooltip": "Configures the haze in the scene." + }, + "haze.hazeRange": { + "tooltip": "How far the haze extends out. This is measured in meters." + }, + "haze.hazeAltitudeEffect": { + "tooltip": "If enabled, this adjusts the haze intensity as it gets higher." + }, + "haze.hazeBaseRef": { + "tooltip": "The base of the altitude range. Measured in entity space." + }, + "haze.hazeCeiling": { + "tooltip": "The ceiling of the altitude range. Measured in entity space." + }, + "haze.hazeColor": { + "tooltip": "The color of the haze." + }, + "haze.hazeBackgroundBlend": { + "tooltip": "How much the skybox shows through the haze. The higher the value, the more it shows through." + }, + "haze.hazeEnableGlare": { + "tooltip": "If enabled, a glare is enabled on the skybox, based on the key light." + }, + "haze.hazeGlareColor": { + "tooltip": "The color of the glare based on the key light." + }, + "haze.hazeGlareAngle": { + "tooltip": "The angular size of the glare and how much it encompasses the skybox, based on the key light." + }, + "bloomMode": { + "tooltip": "Configures how much bright areas of the scene glow." + }, + "bloom.bloomIntensity": { + "tooltip": "The intensity, or brightness, of the bloom effect." + }, + "bloom.bloomThreshold": { + "tooltip": "The cutoff of the bloom. The higher the value, the more only bright areas of the scene will glow." + }, + "bloom.bloomSize": { + "tooltip": "The radius of bloom. The higher the value, the larger the bloom." + }, + "avatarPriority": { + "tooltip": "Alter Avatars' update priorities." + }, + "screenshare": { + "tooltip": "Enable screen-sharing within this zone" + }, + "modelURL": { + "tooltip": "A mesh model from an FBX or OBJ file." + }, + "shapeType": { + "tooltip": "The shape of the collision hull used if collisions are enabled. This affects how an entity collides." + }, + "compoundShapeURL": { + "tooltip": "The model file to use for the compound shape if Collision Shape is \"Compound\"." + }, + "animation.url": { + "tooltip": "An animation to play on the model." + }, + "animation.running": { + "tooltip": "If enabled, the animation on the model will play automatically." + }, + "animation.allowTranslation": { + "tooltip": "If enabled, this allows an entity to move in space during an animation." + }, + "animation.loop": { + "tooltip": "If enabled, then the animation will continuously repeat." + }, + "animation.hold": { + "tooltip": "If enabled, then rotations and translations of the last frame played are maintained when the animation stops." + }, + "animation.currentFrame": { + "tooltip": "The current frame being played in the animation." + }, + "animation.firstFrame": { + "tooltip": "The first frame to play in the animation." + }, + "animation.lastFrame": { + "tooltip": "The last frame to play in the animation." + }, + "animation.fps": { + "tooltip": "The speed of the animation." + }, + "textures": { + "tooltip": "A JSON string containing a texture. Use a name from the Original Texture property to override it." + }, + "originalTextures": { + "tooltip": "A JSON string containing the original texture used on the model." + }, + "imageURL": { + "tooltip": "The URL for the image source." + }, + "imageColor": { + "tooltip": "The tint to be applied to the image.", + "jsPropertyName": "color" + }, + "imageAlpha": { + "tooltip": "The opacity of the image between 0.0 fully transparent and 1.0 completely opaque." + }, + "emissive": { + "tooltip": "If enabled, the image will display at full brightness." + }, + "subImage": { + "tooltip": "The area of the image that is displayed." + }, + "imageBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "keepAspectRatio": { + "tooltip": "If enabled, the image will maintain its original aspect ratio." + }, + "sourceUrl": { + "tooltip": "The URL for the web page source." + }, + "dpi": { + "tooltip": "The resolution to display the page at, in pixels per inch. Use this to resize your web source in the frame." + }, + "webBillboardMode": { + "tooltip": "If enabled, determines how the entity will face the camera.", + "jsPropertyName": "billboardMode" + }, + "inputMode": { + "tooltip": "The user input mode to use." + }, + "showKeyboardFocusHighlight": { + "tooltip": "If enabled, highlights when it has keyboard focus." + }, + "isEmitting": { + "tooltip": "If enabled, then particles are emitted." + }, + "lifespan": { + "tooltip": "How long each particle lives, measured in seconds." + }, + "maxParticles": { + "tooltip": "The maximum number of particles to render at one time. Older particles are swapped out for new ones." + }, + "particleTextures": { + "tooltip": "The URL of a JPG or PNG image file to display for each particle.", + "jsPropertyName": "textures" + }, + "emitRate": { + "tooltip": "The number of particles per second to emit." + }, + "emitSpeed": { + "tooltip": "The speed that each particle is emitted at, measured in m/s." + }, + "speedSpread": { + "tooltip": "The spread in speeds at which particles are emitted at, resulting in a variety of speeds." + }, + "particleShapeType": { + "tooltip": "The shape of the surface from which to emit particles.", + "jsPropertyName": "shapeType" + }, + "particleCompoundShapeURL": { + "tooltip": "The model file to use for the particle emitter if Shape Type is \"Use Compound Shape URL\".", + "jsPropertyName": "compoundShapeURL" + }, + "emitDimensions": { + "tooltip": "The outer limit radius in dimensions that the particles can be emitted from." + }, + "emitOrientation": { + "tooltip": "The orientation of particle emission relative to the entity's axes." + }, + "emitRadiusStart": { + "tooltip": "The inner limit radius in dimensions that the particles start emitting from." + }, + "emitterShouldTrail": { + "tooltip": "If enabled, then particles are \"left behind\" as the emitter moves, otherwise they are not." + }, + "particleRadiusTriple": { + "tooltip": "The size of each particle.", + "jsPropertyName": "particleRadius" + }, + "particleRadius": { + "tooltip": "The size of each particle." + }, + "radiusStart": { + "tooltip": "The start size of each particle." + }, + "radiusFinish": { + "tooltip": "The finish size of each particle." + }, + "radiusSpread": { + "tooltip": "The spread in size that each particle is given, resulting in a variety of sizes." + }, + "particleColorTriple": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "particleColor": { + "tooltip": "The color of each particle.", + "jsPropertyName": "color" + }, + "colorStart": { + "tooltip": "The start color of each particle." + }, + "colorFinish": { + "tooltip": "The finish color of each particle." + }, + "colorSpread": { + "tooltip": "The spread in color that each particle is given, resulting in a variety of colors." + }, + "particleAlphaTriple": { + "tooltip": "The opacity of each particle between 0.0 fully transparent and 1.0 completely opaque.", + "jsPropertyName": "alpha" + }, + "alpha": { + "tooltip": "The opacity of each particle between 0.0 fully transparent and 1.0 completely opaque." + }, + "alphaStart": { + "tooltip": "The initial opacity level of each particle between 0.0 fully transparent and 1.0 completely opaque." + }, + "alphaFinish": { + "tooltip": "The final opacity level of each particle between 0.0 fully transparent and 1.0 completely opaque." + }, + "alphaSpread": { + "tooltip": "The spread in opacity that each particle is given, resulting in a variety of opacity levels." + }, + "emitAcceleration": { + "tooltip": "The acceleration that is applied to each particle during its lifetime." + }, + "accelerationSpread": { + "tooltip": "The spread in accelerations that each particle is given, resulting in a variety of accelerations." + }, + "particleSpinTriple": { + "tooltip": "The spin of each particle.", + "jsPropertyName": "particleSpin" + }, + "particleSpin": { + "tooltip": "The spin of each particle." + }, + "spinStart": { + "tooltip": "The start spin of each particle." + }, + "spinFinish": { + "tooltip": "The finish spin of each particle." + }, + "spinSpread": { + "tooltip": "The spread in spin that each particle is given, resulting in a variety of spins." + }, + "rotateWithEntity": { + "tooltip": "If enabled, each particle will spin relative to the rotation of the entity as a whole." + }, + "particlePolarTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis.", + "skipJSProperty": true + }, + "polarStart": { + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "polarFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -z direction, and rotates around its y axis." + }, + "particleAzimuthTriple": { + "tooltip": "The angle range in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis.", + "skipJSProperty": true + }, + "azimuthStart": { + "tooltip": "The start angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." + }, + "azimuthFinish": { + "tooltip": "The finish angle in deg at which particles are emitted. Starts in the entity's -x direction, and rotates around its z axis." + }, + "lightColor": { + "tooltip": "The color of the light emitted.", + "jsPropertyName": "color" + }, + "intensity": { + "tooltip": "The brightness of the light." + }, + "falloffRadius": { + "tooltip": "The distance from the light's center where the intensity is reduced." + }, + "isSpotlight": { + "tooltip": "If enabled, then the light is directional, otherwise the light is a point light which emits light in all directions." + }, + "exponent": { + "tooltip": "Affects the softness of the spotlight beam; the higher the value, the softer the beam." + }, + "cutoff": { + "tooltip": "Affects the size of the spotlight beam; the higher the value, the larger the beam." + }, + "materialURL": { + "tooltip": "The URL to an external JSON file or \"materialData\", \"materialData? to use Material Data." + }, + "materialData": { + "tooltip": "Can be used instead of a JSON file when material set to materialData." + }, + "parentMaterialName": { + "tooltip": "The target mesh indices or material names that this material entity should be assigned to on it's parent. This only supports parents that are Avatars as well as Shape or Model entity types." + }, + "priority": { + "tooltip": "The priority of the material, where a larger number means higher priority. Original materials = 0." + }, + "materialMappingMode": { + "tooltip": "How the material is mapped to the entity. If set to \"UV space\", then the material will be applied with the target entity's UV coordinates. If set to \"3D Projected\", then the 3D transform of the material entity will be used." + }, + "materialMappingPos": { + "tooltip": "The offset position of the bottom left of the material within the parent's UV space." + }, + "materialMappingScale": { + "tooltip": "How many times the material will repeat in each direction within the parent's UV space." + }, + "materialMappingRot": { + "tooltip": "How much to rotate the material within the parent's UV-space, in degrees." + }, + "materialRepeat": { + "tooltip": "If enabled, the material will repeat, otherwise it will clamp." + }, + "followCamera": { + "tooltip": "If enabled, the grid is always visible even as the camera moves to another position." + }, + "majorGridEvery": { + "tooltip": "The number of \"Minor Grid Every\" intervals at which to draw a thick grid line." + }, + "minorGridEvery": { + "tooltip": "The real number of meters at which to draw thin grid lines." + }, + "id": { + "tooltip": "The unique identifier of this entity." + }, + "name": { + "tooltip": "The name of this entity." + }, + "description": { + "tooltip": "Use this field to describe the entity." + }, + "position": { + "tooltip": "The global position of this entity." + }, + "localPosition": { + "tooltip": "The local position of this entity." + }, + "rotation": { + "tooltip": "The global rotation of this entity." + }, + "localRotation": { + "tooltip": "The local rotation of this entity." + }, + "dimensions": { + "tooltip": "The global dimensions of this entity." + }, + "localDimensions": { + "tooltip": "The local dimensions of this entity." + }, + "scale": { + "tooltip": "The global scaling of this entity.", + "skipJSProperty": true + }, + "registrationPoint": { + "tooltip": "The point in the entity at which the entity is rotated about." + }, + "visible": { + "tooltip": "If enabled, this entity will be visible." + }, + "locked": { + "tooltip": "If enabled, this entity will be locked." + }, + "collisionless": { + "tooltip": "If enabled, this entity will collide with other entities or avatars." + }, + "dynamic": { + "tooltip": "If enabled, this entity has collisions associated with it that can affect its movement." + }, + "collidesWithStatic": { + "tooltip": "If enabled, this entity will collide with other non-moving, static entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithDynamic": { + "tooltip": "If enabled, this entity will collide with other dynamic entities.", + "jsPropertyName": "collidesWith" + }, + "collidesWithKinematic": { + "tooltip": "If enabled, this entity will collide with other kinematic entities (they have velocity but are not dynamic).", + "jsPropertyName": "collidesWith" + }, + "collidesWithOtherAvatar": { + "tooltip": "If enabled, this entity will collide with other user's avatars.", + "jsPropertyName": "collidesWith" + }, + "collidesWithMyAvatar": { + "tooltip": "If enabled, this entity will collide with your own avatar.", + "jsPropertyName": "collidesWith" + }, + "collisionSoundURL": { + "tooltip": "The URL of a sound to play when the entity collides with something else." + }, + "grab.grabbable": { + "tooltip": "If enabled, this entity will allow grabbing input and will be movable." + }, + "grab.triggerable": { + "tooltip": "If enabled, the collider on this entity is used for triggering events." + }, + "cloneable": { + "tooltip": "If enabled, this entity can be duplicated." + }, + "cloneLifetime": { + "tooltip": "The lifetime for clones of this entity." + }, + "cloneLimit": { + "tooltip": "The total number of clones of this entity that can exist in the domain at any given time." + }, + "cloneDynamic": { + "tooltip": "If enabled, then clones created from this entity will be dynamic, allowing the clone to collide." + }, + "cloneAvatarEntity": { + "tooltip": "If enabled, then clones created from this entity will be created as avatar entities." + }, + "grab.grabFollowsController": { + "tooltip": "If enabled, grabbed entities will follow the movements of your hand controller instead of your avatar's hand." + }, + "canCastShadow": { + "tooltip": "If enabled, the geometry of this entity casts shadows when a shadow-casting light source shines on it. Note: Shadows are rendered only on high-profiled computers. This setting will have no effect on computers profiled to medium or low graphics.." + }, + "ignorePickIntersection": { + "tooltip": "If enabled, this entity will not be considered for ray picks, and will also not occlude other entities when picking." + }, + "parentID": { + "tooltip": "The ID of the entity or avatar that this entity is parented to." + }, + "parentJointIndex": { + "tooltip": "If the entity is parented to an avatar, this joint defines where on the avatar the entity is parented." + }, + "href": { + "tooltip": "The URL that will be opened when a user clicks on this entity. Useful for web pages and portals." + }, + "script": { + "tooltip": "The URL to an external JS file to add behaviors to the client." + }, + "serverScripts": { + "tooltip": "The URL to an external JS file to add behaviors to the server." + }, + "serverScriptsStatus": { + "tooltip": "The status of the server script, if provided. This shows if it's running or has an error.", + "skipJSProperty": true + }, + "hasLifetime": { + "tooltip": "If enabled, the entity will disappear after a certain amount of time specified by Lifetime.", + "jsPropertyName": "lifetime" + }, + "lifetime": { + "tooltip": "The time this entity will exist in the environment for." + }, + "userData": { + "tooltip": "Used to store extra data about the entity in JSON format." + }, + "localVelocity": { + "tooltip": "The linear velocity vector of the entity. The velocity at which this entity moves forward in space." + }, + "damping": { + "tooltip": "The linear damping to slow down the linear velocity of an entity over time." + }, + "localAngularVelocity": { + "tooltip": "The angular velocity of the entity in rad/s with respect to its axes, about its pivot point." + }, + "angularDamping": { + "tooltip": "The angular damping to slow down the angular velocity of an entity over time." + }, + "restitution": { + "tooltip": "If enabled, the entity can bounce against other objects that also have Bounciness." + }, + "friction": { + "tooltip": "The friction applied to slow down an entity when it's moving against another entity." + }, + "density": { + "tooltip": "The density of the entity. The higher the density, the harder the entity is to move." + }, + "gravity": { + "tooltip": "The acceleration due to gravity that the entity should move with, in world space." + }, + "renderLayer": { + "tooltip": "The layer on which this entity is rendered." + }, + "primitiveMode": { + "tooltip": "The mode in which to draw an entity, either \"Solid\" or \"Wireframe\"." + }, + "renderWithZones": { + "tooltip": "If set, this entity will only render when your avatar is inside of a zone in this list." + }, + "groupCulled": { + "tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all." + }, + "webColor": { + "tooltip": "The tint of the web entity." + }, + "webAlpha": { + "tooltip": "The opacity of the web entity between 0.0 fully transparent and 1.0 completely opaque." + }, + "useBackground": { + "tooltip": "If disabled, this web entity will support a transparent background for the webpage if the CSS property of the 'body' is set with 'background-color: transparent;'" + }, + "maxFPS": { + "tooltip": "The FPS at which to render the web entity. Higher values will have a performance impact." + }, + "scriptURL": { + "tooltip": "The URL of a script to inject into the web page." + }, + "alignToGrid": { + "tooltip": "Used to align entities to the grid, or floor of the environment.", + "skipJSProperty": true + }, + "createModel": { + "tooltip": "An entity that is based on a custom mesh created from an .OBJ or .FBX.", + "skipJSProperty": true + }, + "createShape": { + "tooltip": "An entity that has many different primitive shapes.", + "skipJSProperty": true + }, + "createLight": { + "tooltip": "An entity that emits light.", + "skipJSProperty": true + }, + "createText": { + "tooltip": "An entity that displays text on a panel.", + "skipJSProperty": true + }, + "createImage": { + "tooltip": "An entity that displays an image on a panel.", + "skipJSProperty": true + }, + "createWeb": { + "tooltip": "An entity that displays a web page on a panel.", + "skipJSProperty": true + }, + "createZone": { + "tooltip": "An entity that can be used for skyboxes, lighting, and can constrain or change avatar behaviors.", + "skipJSProperty": true + }, + "createParticle": { + "tooltip": "An entity that emits particles.", + "skipJSProperty": true + }, + "createMaterial": { + "tooltip": "An entity that creates a material that can be attached to a Shape or Model.", + "skipJSProperty": true + }, + "useAssetServer": { + "tooltip": "A server that hosts content and assets. You can't take items that are hosted here into other domains.", + "skipJSProperty": true + }, + "importNewEntity": { + "tooltip": "Import a local or hosted file that can be used across domains.", + "skipJSProperty": true + } +} From c6cf0b8c33936554f83d7347c9d27b8e53b411bf Mon Sep 17 00:00:00 2001 From: Alezia Kurdis <60075796+AleziaKurdis@users.noreply.github.com> Date: Tue, 18 Aug 2020 23:22:39 -0400 Subject: [PATCH 072/388] New "useBackground" property on Web Entity New "useBackground" property on Web Entity. --- .../html/js/entityProperties.js | 9435 +++++++++-------- 1 file changed, 4720 insertions(+), 4715 deletions(-) diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js index 182dddf817..6a6e4d6f66 100644 --- a/scripts/system/create/entityProperties/html/js/entityProperties.js +++ b/scripts/system/create/entityProperties/html/js/entityProperties.js @@ -1,4715 +1,4720 @@ -// entityProperties.js -// -// Created by Ryan Huffman on 13 Nov 2014 -// Copyright 2014 High Fidelity, Inc. -// Copyright 2020 Vircadia contributors. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html - -/* global alert, augmentSpinButtons, clearTimeout, console, document, Element, - EventBridge, JSONEditor, openEventBridge, setTimeout, window, _, $ */ - -var currentTab = "base"; - -const DEGREES_TO_RADIANS = Math.PI / 180.0; - -const NO_SELECTION = ","; - -const PROPERTY_SPACE_MODE = Object.freeze({ - ALL: 0, - LOCAL: 1, - WORLD: 2 -}); - -const PROPERTY_SELECTION_VISIBILITY = Object.freeze({ - SINGLE_SELECTION: 1, - MULTIPLE_SELECTIONS: 2, - MULTI_DIFF_SELECTIONS: 4, - ANY_SELECTIONS: 7 /* SINGLE_SELECTION | MULTIPLE_SELECTIONS | MULTI_DIFF_SELECTIONS */ -}); - -// Multiple-selection behavior -const PROPERTY_MULTI_DISPLAY_MODE = Object.freeze({ - DEFAULT: 0, - /** - * Comma separated values - * Limited for properties with type "string" or "textarea" and readOnly enabled - */ - COMMA_SEPARATED_VALUES: 1 -}); - -const GROUPS = [ - { - id: "base", - label: "ENTITY", - properties: [ - { - label: NO_SELECTION, - type: "icon", - icons: ENTITY_TYPE_ICON, - propertyID: "type", - replaceID: "placeholder-property-type", - }, - { - label: "Name", - type: "string", - propertyID: "name", - placeholder: "Name", - replaceID: "placeholder-property-name", - }, - { - label: "ID", - type: "string", - propertyID: "id", - placeholder: "ID", - readOnly: true, - replaceID: "placeholder-property-id", - multiDisplayMode: PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES, - }, - { - label: "Description", - type: "string", - propertyID: "description", - }, - { - label: "Parent", - type: "string", - propertyID: "parentID", - onChange: parentIDChanged, - }, - { - label: "Parent Joint Index", - type: "number", - propertyID: "parentJointIndex", - }, - { - label: "", - glyph: "", - type: "bool", - propertyID: "locked", - replaceID: "placeholder-property-locked", - }, - { - label: "", - glyph: "", - type: "bool", - propertyID: "visible", - replaceID: "placeholder-property-visible", - }, - { - label: "Render Layer", - type: "dropdown", - options: { - world: "World", - front: "Front", - hud: "HUD" - }, - propertyID: "renderLayer", - }, - { - label: "Primitive Mode", - type: "dropdown", - options: { - solid: "Solid", - lines: "Wireframe", - }, - propertyID: "primitiveMode", - }, - { - label: "Render With Zones", - type: "multipleZonesSelection", - propertyID: "renderWithZones", - } - ] - }, - { - id: "shape", - label: "SHAPE", - properties: [ - { - label: "Shape", - type: "dropdown", - options: { Cube: "Box", Sphere: "Sphere", Tetrahedron: "Tetrahedron", Octahedron: "Octahedron", - Icosahedron: "Icosahedron", Dodecahedron: "Dodecahedron", Hexagon: "Hexagon", - Triangle: "Triangle", Octagon: "Octagon", Cylinder: "Cylinder", Cone: "Cone", - Circle: "Circle", Quad: "Quad" }, - propertyID: "shape", - }, - { - label: "Color", - type: "color", - propertyID: "color", - }, - { - label: "Alpha", - type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, - propertyID: "shapeAlpha", - propertyName: "alpha", - }, - ] - }, - { - id: "text", - label: "TEXT", - properties: [ - { - label: "Text", - type: "string", - propertyID: "text", - }, - { - label: "Text Color", - type: "color", - propertyID: "textColor", - }, - { - label: "Text Alpha", - type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, - propertyID: "textAlpha", - }, - { - label: "Background Color", - type: "color", - propertyID: "backgroundColor", - }, - { - label: "Background Alpha", - type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, - propertyID: "backgroundAlpha", - }, - { - label: "Line Height", - type: "number-draggable", - min: 0, - step: 0.001, - decimals: 4, - unit: "m", - propertyID: "lineHeight", - }, - { - label: "Font", - type: "string", - propertyID: "font", - }, - { - label: "Effect", - type: "dropdown", - options: { - none: "None", - outline: "Outline", - "outline fill": "Outline with fill", - shadow: "Shadow" - }, - propertyID: "textEffect", - }, - { - label: "Effect Color", - type: "color", - propertyID: "textEffectColor", - }, - { - label: "Effect Thickness", - type: "number-draggable", - min: 0.0, - max: 0.5, - step: 0.01, - decimals: 2, - propertyID: "textEffectThickness", - }, - { - label: "Billboard Mode", - type: "dropdown", - options: { none: "None", yaw: "Yaw", full: "Full"}, - propertyID: "textBillboardMode", - propertyName: "billboardMode", // actual entity property name - }, - { - label: "Top Margin", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "topMargin", - }, - { - label: "Right Margin", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "rightMargin", - }, - { - label: "Bottom Margin", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "bottomMargin", - }, - { - label: "Left Margin", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "leftMargin", - }, - { - label: "Unlit", - type: "bool", - propertyID: "unlit", - } - ] - }, - { - id: "zone", - label: "ZONE", - properties: [ - { - label: "Shape Type", - type: "dropdown", - options: { "box": "Box", "sphere": "Sphere", "ellipsoid": "Ellipsoid", - "cylinder-y": "Cylinder", "compound": "Use Compound Shape URL" }, - propertyID: "zoneShapeType", - propertyName: "shapeType", // actual entity property name - }, - { - label: "Compound Shape URL", - type: "string", - propertyID: "zoneCompoundShapeURL", - propertyName: "compoundShapeURL", // actual entity property name - }, - { - label: "Flying Allowed", - type: "bool", - propertyID: "flyingAllowed", - }, - { - label: "Ghosting Allowed", - type: "bool", - propertyID: "ghostingAllowed", - }, - { - label: "Filter", - type: "string", - propertyID: "filterURL", - } - ] - }, - { - id: "zone_key_light", - label: "ZONE KEY LIGHT", - properties: [ - { - label: "Key Light", - type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyID: "keyLightMode", - - }, - { - label: "Key Light Color", - type: "color", - propertyID: "keyLight.color", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Light Intensity", - type: "number-draggable", - min: -40, - max: 40, - step: 0.01, - decimals: 2, - propertyID: "keyLight.intensity", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Light Horizontal Angle", - type: "number-draggable", - step: 0.1, - multiplier: DEGREES_TO_RADIANS, - decimals: 2, - unit: "deg", - propertyID: "keyLight.direction.y", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Light Vertical Angle", - type: "number-draggable", - step: 0.1, - multiplier: DEGREES_TO_RADIANS, - decimals: 2, - unit: "deg", - propertyID: "keyLight.direction.x", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Cast Shadows", - type: "bool", - propertyID: "keyLight.castShadows", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Shadow Bias", - type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, - propertyID: "keyLight.shadowBias", - showPropertyRule: { "keyLightMode": "enabled" }, - }, - { - label: "Shadow Max Distance", - type: "number-draggable", - min: 0, - max: 250, - step: 0.1, - decimals: 2, - propertyID: "keyLight.shadowMaxDistance", - showPropertyRule: { "keyLightMode": "enabled" }, - } - ] - }, - { - id: "zone_skybox", - label: "ZONE SKYBOX", - properties: [ - { - label: "Skybox", - type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyID: "skyboxMode", - }, - { - label: "Skybox Color", - type: "color", - propertyID: "skybox.color", - showPropertyRule: { "skyboxMode": "enabled" }, - }, - { - label: "Skybox Source", - type: "string", - propertyID: "skybox.url", - showPropertyRule: { "skyboxMode": "enabled" }, - } - ] - }, - { - id: "zone_ambient_light", - label: "ZONE AMBIENT LIGHT", - properties: [ - { - label: "Ambient Light", - type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyID: "ambientLightMode", - }, - { - label: "Ambient Intensity", - type: "number-draggable", - min: -200, - max: 200, - step: 0.1, - decimals: 2, - propertyID: "ambientLight.ambientIntensity", - showPropertyRule: { "ambientLightMode": "enabled" }, - }, - { - label: "Ambient Source", - type: "string", - propertyID: "ambientLight.ambientURL", - showPropertyRule: { "ambientLightMode": "enabled" }, - }, - { - type: "buttons", - buttons: [ { id: "copy", label: "Copy from Skybox", - className: "black", onClick: copySkyboxURLToAmbientURL } ], - propertyID: "copyURLToAmbient", - showPropertyRule: { "ambientLightMode": "enabled" }, - } - ] - }, - { - id: "zone_haze", - label: "ZONE HAZE", - properties: [ - { - label: "Haze", - type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyID: "hazeMode", - }, - { - label: "Range", - type: "number-draggable", - min: 1, - max: 10000, - step: 1, - decimals: 0, - unit: "m", - propertyID: "haze.hazeRange", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Use Altitude", - type: "bool", - propertyID: "haze.hazeAltitudeEffect", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Base", - type: "number-draggable", - min: -16000, - max: 16000, - step: 1, - decimals: 0, - unit: "m", - propertyID: "haze.hazeBaseRef", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Ceiling", - type: "number-draggable", - min: -16000, - max: 16000, - step: 1, - decimals: 0, - unit: "m", - propertyID: "haze.hazeCeiling", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Haze Color", - type: "color", - propertyID: "haze.hazeColor", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Background Blend", - type: "number-draggable", - min: 0, - max: 1, - step: 0.001, - decimals: 3, - propertyID: "haze.hazeBackgroundBlend", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Enable Glare", - type: "bool", - propertyID: "haze.hazeEnableGlare", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Glare Color", - type: "color", - propertyID: "haze.hazeGlareColor", - showPropertyRule: { "hazeMode": "enabled" }, - }, - { - label: "Glare Angle", - type: "number-draggable", - min: 0, - max: 180, - step: 1, - decimals: 0, - propertyID: "haze.hazeGlareAngle", - showPropertyRule: { "hazeMode": "enabled" }, - } - ] - }, - { - id: "zone_bloom", - label: "ZONE BLOOM", - properties: [ - { - label: "Bloom", - type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyID: "bloomMode", - }, - { - label: "Bloom Intensity", - type: "number-draggable", - min: 0, - max: 1, - step: 0.001, - decimals: 3, - propertyID: "bloom.bloomIntensity", - showPropertyRule: { "bloomMode": "enabled" }, - }, - { - label: "Bloom Threshold", - type: "number-draggable", - min: 0, - max: 1, - step: 0.001, - decimals: 3, - propertyID: "bloom.bloomThreshold", - showPropertyRule: { "bloomMode": "enabled" }, - }, - { - label: "Bloom Size", - type: "number-draggable", - min: 0, - max: 2, - step: 0.001, - decimals: 3, - propertyID: "bloom.bloomSize", - showPropertyRule: { "bloomMode": "enabled" }, - } - ] - }, - { - id: "zone_avatar_priority", - label: "ZONE AVATAR PRIORITY", - properties: [ - { - label: "Avatar Priority", - type: "dropdown", - options: { inherit: "Inherit", crowd: "Crowd", hero: "Hero" }, - propertyID: "avatarPriority", - }, - { - label: "Screen-share", - type: "dropdown", - options: { inherit: "Inherit", disabled: "Off", enabled: "On" }, - propertyID: "screenshare", - } - ] - }, - { - id: "model", - label: "MODEL", - properties: [ - { - label: "Model", - type: "string", - placeholder: "URL", - propertyID: "modelURL", - hideIfCertified: true, - }, - { - label: "Collision Shape", - type: "dropdown", - options: { "none": "No Collision", "box": "Box", "sphere": "Sphere", "compound": "Compound" , - "simple-hull": "Basic - Whole model", "simple-compound": "Good - Sub-meshes" , - "static-mesh": "Exact - All polygons (non-dynamic only)" }, - propertyID: "shapeType", - }, - { - label: "Compound Shape", - type: "string", - propertyID: "compoundShapeURL", - hideIfCertified: true, - }, - { - label: "Animation", - type: "string", - propertyID: "animation.url", - hideIfCertified: true, - }, - { - label: "Play Automatically", - type: "bool", - propertyID: "animation.running", - }, - { - label: "Loop", - type: "bool", - propertyID: "animation.loop", - }, - { - label: "Allow Translation", - type: "bool", - propertyID: "animation.allowTranslation", - }, - { - label: "Hold", - type: "bool", - propertyID: "animation.hold", - }, - { - label: "Animation Frame", - type: "number-draggable", - propertyID: "animation.currentFrame", - }, - { - label: "First Frame", - type: "number-draggable", - propertyID: "animation.firstFrame", - }, - { - label: "Last Frame", - type: "number-draggable", - propertyID: "animation.lastFrame", - }, - { - label: "Animation FPS", - type: "number-draggable", - propertyID: "animation.fps", - }, - { - label: "Texture", - type: "textarea", - propertyID: "textures", - }, - { - label: "Original Texture", - type: "textarea", - propertyID: "originalTextures", - readOnly: true, - hideIfCertified: true, - }, - { - label: "Group Culled", - type: "bool", - propertyID: "groupCulled", - } - ] - }, - { - id: "image", - label: "IMAGE", - properties: [ - { - label: "Image", - type: "string", - placeholder: "URL", - propertyID: "imageURL", - }, - { - label: "Color", - type: "color", - propertyID: "imageColor", - propertyName: "color", // actual entity property name - }, - { - label: "Alpha", - type: "number-draggable", - min: 0, - max: 1, - step: 0.01, - decimals: 2, - propertyID: "imageAlpha", - propertyName: "alpha", - }, - { - label: "Emissive", - type: "bool", - propertyID: "emissive", - }, - { - label: "Sub Image", - type: "rect", - min: 0, - step: 1, - subLabels: [ "x", "y", "w", "h" ], - propertyID: "subImage", - }, - { - label: "Billboard Mode", - type: "dropdown", - options: { none: "None", yaw: "Yaw", full: "Full"}, - propertyID: "imageBillboardMode", - propertyName: "billboardMode", // actual entity property name - }, - { - label: "Keep Aspect Ratio", - type: "bool", - propertyID: "keepAspectRatio", - } - ] - }, - { - id: "web", - label: "WEB", - properties: [ - { - label: "Source", - type: "string", - propertyID: "sourceUrl", - }, - { - label: "Source Resolution", - type: "number-draggable", - propertyID: "dpi", - }, - { - label: "Web Color", - type: "color", - propertyID: "webColor", - propertyName: "color", // actual entity property name - }, - { - label: "Web Alpha", - type: "number-draggable", - step: 0.001, - decimals: 3, - propertyID: "webAlpha", - propertyName: "alpha", - min: 0, - max: 1, - }, - { - label: "Max FPS", - type: "number-draggable", - step: 1, - decimals: 0, - propertyID: "maxFPS", - }, - { - label: "Billboard Mode", - type: "dropdown", - options: { none: "None", yaw: "Yaw", full: "Full"}, - propertyID: "webBillboardMode", - propertyName: "billboardMode", // actual entity property name - }, - { - label: "Input Mode", - type: "dropdown", - options: { - touch: "Touch events", - mouse: "Mouse events" - }, - propertyID: "inputMode", - }, - { - label: "Focus Highlight", - type: "bool", - propertyID: "showKeyboardFocusHighlight", - }, - { - label: "Script URL", - type: "string", - propertyID: "scriptURL", - placeholder: "URL", - } - ] - }, - { - id: "light", - label: "LIGHT", - properties: [ - { - label: "Light Color", - type: "color", - propertyID: "lightColor", - propertyName: "color", // actual entity property name - }, - { - label: "Intensity", - type: "number-draggable", - min: -1000, - max: 10000, - step: 0.1, - decimals: 2, - propertyID: "intensity", - }, - { - label: "Fall-Off Radius", - type: "number-draggable", - min: 0, - max: 10000, - step: 0.1, - decimals: 2, - unit: "m", - propertyID: "falloffRadius", - }, - { - label: "Spotlight", - type: "bool", - propertyID: "isSpotlight", - }, - { - label: "Spotlight Exponent", - type: "number-draggable", - min: 0, - step: 0.01, - decimals: 2, - propertyID: "exponent", - }, - { - label: "Spotlight Cut-Off", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "cutoff", - } - ] - }, - { - id: "material", - label: "MATERIAL", - properties: [ - { - label: "Material URL", - type: "string", - propertyID: "materialURL", - }, - { - label: "Material Data", - type: "textarea", - buttons: [ { id: "clear", label: "Clear Material Data", className: "red", onClick: clearMaterialData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONMaterialEditor }, - { id: "save", label: "Save Material Data", className: "black", onClick: saveMaterialData } ], - propertyID: "materialData", - }, - { - label: "Material Target", - type: "dynamic-multiselect", - propertyUpdate: materialTargetPropertyUpdate, - propertyID: "parentMaterialName", - selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION, - }, - { - label: "Priority", - type: "number-draggable", - min: 0, - propertyID: "priority", - }, - { - label: "Material Mapping Mode", - type: "dropdown", - options: { - uv: "UV space", projected: "3D projected" - }, - propertyID: "materialMappingMode", - }, - { - label: "Material Position", - type: "vec2", - vec2Type: "xyz", - min: 0, - max: 1, - step: 0.1, - decimals: 4, - subLabels: [ "x", "y" ], - propertyID: "materialMappingPos", - }, - { - label: "Material Scale", - type: "vec2", - vec2Type: "xyz", - min: 0, - step: 0.1, - decimals: 4, - subLabels: [ "x", "y" ], - propertyID: "materialMappingScale", - }, - { - label: "Material Rotation", - type: "number-draggable", - step: 0.1, - decimals: 2, - unit: "deg", - propertyID: "materialMappingRot", - }, - { - label: "Material Repeat", - type: "bool", - propertyID: "materialRepeat", - } - ] - }, - { - id: "grid", - label: "GRID", - properties: [ - { - label: "Color", - type: "color", - propertyID: "gridColor", - propertyName: "color", // actual entity property name - }, - { - label: "Follow Camera", - type: "bool", - propertyID: "followCamera", - }, - { - label: "Major Grid Every", - type: "number-draggable", - min: 0, - step: 1, - decimals: 0, - propertyID: "majorGridEvery", - }, - { - label: "Minor Grid Every", - type: "number-draggable", - min: 0, - step: 0.01, - decimals: 2, - propertyID: "minorGridEvery", - } - ] - }, - { - id: "particles", - label: "PARTICLES", - properties: [ - { - label: "Emit", - type: "bool", - propertyID: "isEmitting", - }, - { - label: "Lifespan", - type: "number-draggable", - unit: "s", - step: 0.01, - decimals: 2, - propertyID: "lifespan", - }, - { - label: "Max Particles", - type: "number-draggable", - step: 1, - propertyID: "maxParticles", - }, - { - label: "Texture", - type: "texture", - propertyID: "particleTextures", - propertyName: "textures", // actual entity property name - } - ] - }, - { - id: "particles_emit", - label: "PARTICLES EMIT", - properties: [ - { - label: "Emit Rate", - type: "number-draggable", - step: 1, - propertyID: "emitRate", - }, - { - label: "Emit Speed", - type: "number-draggable", - step: 0.1, - decimals: 2, - propertyID: "emitSpeed", - }, - { - label: "Speed Spread", - type: "number-draggable", - step: 0.1, - decimals: 2, - propertyID: "speedSpread", - }, - { - label: "Shape Type", - type: "dropdown", - options: { "box": "Box", "ellipsoid": "Ellipsoid", - "cylinder-y": "Cylinder", "circle": "Circle", "plane": "Plane", - "compound": "Use Compound Shape URL" }, - propertyID: "particleShapeType", - propertyName: "shapeType", - }, - { - label: "Compound Shape URL", - type: "string", - propertyID: "particleCompoundShapeURL", - propertyName: "compoundShapeURL", - }, - { - label: "Emit Dimensions", - type: "vec3", - vec3Type: "xyz", - step: 0.01, - round: 100, - subLabels: [ "x", "y", "z" ], - propertyID: "emitDimensions", - }, - { - label: "Emit Radius Start", - type: "number-draggable", - step: 0.001, - decimals: 3, - propertyID: "emitRadiusStart" - }, - { - label: "Emit Orientation", - type: "vec3", - vec3Type: "pyr", - step: 0.01, - round: 100, - subLabels: [ "x", "y", "z" ], - unit: "deg", - propertyID: "emitOrientation", - }, - { - label: "Trails", - type: "bool", - propertyID: "emitterShouldTrail", - } - ] - }, - { - id: "particles_size", - label: "PARTICLES SIZE", - properties: [ - { - type: "triple", - label: "Size", - propertyID: "particleRadiusTriple", - properties: [ - { - label: "Start", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "radiusStart", - fallbackProperty: "particleRadius", - }, - { - label: "Middle", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "particleRadius", - }, - { - label: "Finish", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "radiusFinish", - fallbackProperty: "particleRadius", - } - ] - }, - { - label: "Size Spread", - type: "number-draggable", - step: 0.01, - decimals: 2, - propertyID: "radiusSpread", - } - ] - }, - { - id: "particles_color", - label: "PARTICLES COLOR", - properties: [ - { - type: "triple", - label: "Color", - propertyID: "particleColorTriple", - properties: [ - { - label: "Start", - type: "color", - propertyID: "colorStart", - fallbackProperty: "color", - }, - { - label: "Middle", - type: "color", - propertyID: "particleColor", - propertyName: "color", // actual entity property name - }, - { - label: "Finish", - type: "color", - propertyID: "colorFinish", - fallbackProperty: "color", - } - ] - }, - { - label: "Color Spread", - type: "color", - propertyID: "colorSpread", - }, - { - type: "triple", - label: "Alpha", - propertyID: "particleAlphaTriple", - properties: [ - { - label: "Start", - type: "number-draggable", - step: 0.001, - decimals: 3, - propertyID: "alphaStart", - fallbackProperty: "alpha", - }, - { - label: "Middle", - type: "number-draggable", - step: 0.001, - decimals: 3, - propertyID: "alpha", - }, - { - label: "Finish", - type: "number-draggable", - step: 0.001, - decimals: 3, - propertyID: "alphaFinish", - fallbackProperty: "alpha", - } - ] - }, - { - label: "Alpha Spread", - type: "number-draggable", - step: 0.001, - decimals: 3, - propertyID: "alphaSpread", - } - ] - }, - { - id: "particles_behavior", - label: "PARTICLES BEHAVIOR", - properties: [ - { - label: "Emit Acceleration", - type: "vec3", - vec3Type: "xyz", - step: 0.01, - round: 100, - subLabels: [ "x", "y", "z" ], - propertyID: "emitAcceleration", - }, - { - label: "Acceleration Spread", - type: "vec3", - vec3Type: "xyz", - step: 0.01, - round: 100, - subLabels: [ "x", "y", "z" ], - propertyID: "accelerationSpread", - }, - { - type: "triple", - label: "Spin", - propertyID: "particleSpinTriple", - properties: [ - { - label: "Start", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "spinStart", - fallbackProperty: "particleSpin", - }, - { - label: "Middle", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "particleSpin", - }, - { - label: "Finish", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "spinFinish", - fallbackProperty: "particleSpin", - } - ] - }, - { - label: "Spin Spread", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "spinSpread", - }, - { - label: "Rotate with Entity", - type: "bool", - propertyID: "rotateWithEntity", - } - ] - }, - { - id: "particles_constraints", - label: "PARTICLES CONSTRAINTS", - properties: [ - { - type: "triple", - label: "Horizontal Angle", - propertyID: "particlePolarTriple", - properties: [ - { - label: "Start", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "polarStart", - }, - { - label: "Finish", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "polarFinish", - } - ], - }, - { - type: "triple", - label: "Vertical Angle", - propertyID: "particleAzimuthTriple", - properties: [ - { - label: "Start", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "azimuthStart", - }, - { - label: "Finish", - type: "number-draggable", - step: 0.1, - decimals: 2, - multiplier: DEGREES_TO_RADIANS, - unit: "deg", - propertyID: "azimuthFinish", - } - ] - } - ] - }, - { - id: "spatial", - label: "SPATIAL", - properties: [ - { - label: "Position", - type: "vec3", - vec3Type: "xyz", - step: 0.1, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "m", - propertyID: "position", - spaceMode: PROPERTY_SPACE_MODE.WORLD, - }, - { - label: "Local Position", - type: "vec3", - vec3Type: "xyz", - step: 0.1, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "m", - propertyID: "localPosition", - spaceMode: PROPERTY_SPACE_MODE.LOCAL, - }, - { - label: "Rotation", - type: "vec3", - vec3Type: "pyr", - step: 0.1, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "deg", - propertyID: "rotation", - spaceMode: PROPERTY_SPACE_MODE.WORLD, - }, - { - label: "Local Rotation", - type: "vec3", - vec3Type: "pyr", - step: 0.1, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "deg", - propertyID: "localRotation", - spaceMode: PROPERTY_SPACE_MODE.LOCAL, - }, - { - label: "Dimensions", - type: "vec3", - vec3Type: "xyz", - step: 0.01, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "m", - propertyID: "dimensions", - spaceMode: PROPERTY_SPACE_MODE.WORLD, - }, - { - label: "Local Dimensions", - type: "vec3", - vec3Type: "xyz", - step: 0.01, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "m", - propertyID: "localDimensions", - spaceMode: PROPERTY_SPACE_MODE.LOCAL, - }, - { - label: "Scale", - type: "number-draggable", - defaultValue: 100, - unit: "%", - buttons: [ { id: "rescale", label: "Rescale", className: "blue", onClick: rescaleDimensions }, - { id: "reset", label: "Reset Dimensions", className: "red", onClick: resetToNaturalDimensions } ], - propertyID: "scale", - }, - { - label: "Pivot", - type: "vec3", - vec3Type: "xyz", - step: 0.001, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "(ratio of dimension)", - propertyID: "registrationPoint", - }, - { - label: "Align", - type: "buttons", - buttons: [ { id: "selection", label: "Selection to Grid", className: "black", onClick: moveSelectionToGrid }, - { id: "all", label: "All to Grid", className: "black", onClick: moveAllToGrid } ], - propertyID: "alignToGrid", - } - ] - }, - { - id: "behavior", - label: "BEHAVIOR", - properties: [ - { - label: "Grabbable", - type: "bool", - propertyID: "grab.grabbable", - }, - { - label: "Cloneable", - type: "bool", - propertyID: "cloneable", - }, - { - label: "Clone Lifetime", - type: "number-draggable", - min: -1, - unit: "s", - propertyID: "cloneLifetime", - showPropertyRule: { "cloneable": "true" }, - }, - { - label: "Clone Limit", - type: "number-draggable", - min: 0, - propertyID: "cloneLimit", - showPropertyRule: { "cloneable": "true" }, - }, - { - label: "Clone Dynamic", - type: "bool", - propertyID: "cloneDynamic", - showPropertyRule: { "cloneable": "true" }, - }, - { - label: "Clone Avatar Entity", - type: "bool", - propertyID: "cloneAvatarEntity", - showPropertyRule: { "cloneable": "true" }, - }, - { - label: "Triggerable", - type: "bool", - propertyID: "grab.triggerable", - }, - { - label: "Follow Controller", - type: "bool", - propertyID: "grab.grabFollowsController", - }, - { - label: "Cast Shadows", - type: "bool", - propertyID: "canCastShadow", - }, - { - label: "Link", - type: "string", - propertyID: "href", - placeholder: "URL", - }, - { - label: "Ignore Pick Intersection", - type: "bool", - propertyID: "ignorePickIntersection", - }, - { - label: "Lifetime", - type: "number", - unit: "s", - propertyID: "lifetime", - } - ] - }, - { - id: "scripts", - label: "SCRIPTS", - properties: [ - { - label: "Script", - type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadScripts } ], - propertyID: "script", - placeholder: "URL", - hideIfCertified: true, - }, - { - label: "Server Script", - type: "string", - buttons: [ { id: "reload", label: "F", className: "glyph", onClick: reloadServerScripts } ], - propertyID: "serverScripts", - placeholder: "URL", - }, - { - label: "Server Script Status", - type: "placeholder", - indentedLabel: true, - propertyID: "serverScriptStatus", - selectionVisibility: PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION, - }, - { - label: "User Data", - type: "textarea", - buttons: [ { id: "clear", label: "Clear User Data", className: "red", onClick: clearUserData }, - { id: "edit", label: "Edit as JSON", className: "blue", onClick: newJSONEditor }, - { id: "save", label: "Save User Data", className: "black", onClick: saveUserData } ], - propertyID: "userData", - } - ] - }, - { - id: "collision", - label: "COLLISION", - properties: [ - { - label: "Collides", - type: "bool", - inverse: true, - propertyID: "collisionless", - }, - { - label: "Static Entities", - type: "bool", - propertyID: "collidesWithStatic", - propertyName: "static", // actual subProperty name - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - }, - { - label: "Kinematic Entities", - type: "bool", - propertyID: "collidesWithKinematic", - propertyName: "kinematic", // actual subProperty name - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - }, - { - label: "Dynamic Entities", - type: "bool", - propertyID: "collidesWithDynamic", - propertyName: "dynamic", // actual subProperty name - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - }, - { - label: "My Avatar", - type: "bool", - propertyID: "collidesWithMyAvatar", - propertyName: "myAvatar", // actual subProperty name - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - }, - { - label: "Other Avatars", - type: "bool", - propertyID: "collidesWithOtherAvatar", - propertyName: "otherAvatar", // actual subProperty name - subPropertyOf: "collidesWith", - showPropertyRule: { "collisionless": "false" }, - }, - { - label: "Collision Sound", - type: "string", - placeholder: "URL", - propertyID: "collisionSoundURL", - showPropertyRule: { "collisionless": "false" }, - hideIfCertified: true, - }, - { - label: "Dynamic", - type: "bool", - propertyID: "dynamic", - } - ] - }, - { - id: "physics", - label: "PHYSICS", - properties: [ - { - label: "Linear Velocity", - type: "vec3", - vec3Type: "xyz", - step: 0.01, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "m/s", - propertyID: "localVelocity", - }, - { - label: "Linear Damping", - type: "number-draggable", - min: 0, - max: 1, - step: 0.001, - decimals: 4, - propertyID: "damping", - }, - { - label: "Angular Velocity", - type: "vec3", - vec3Type: "pyr", - multiplier: DEGREES_TO_RADIANS, - decimals: 4, - subLabels: [ "x", "y", "z" ], - unit: "deg/s", - propertyID: "localAngularVelocity", - }, - { - label: "Angular Damping", - type: "number-draggable", - min: 0, - max: 1, - step: 0.001, - decimals: 4, - propertyID: "angularDamping", - }, - { - label: "Bounciness", - type: "number-draggable", - step: 0.001, - decimals: 4, - propertyID: "restitution", - }, - { - label: "Friction", - type: "number-draggable", - step: 0.01, - decimals: 4, - propertyID: "friction", - }, - { - label: "Density", - type: "number-draggable", - step: 1, - decimals: 4, - propertyID: "density", - }, - { - label: "Gravity", - type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - step: 0.1, - decimals: 4, - unit: "m/s2", - propertyID: "gravity", - }, - { - label: "Acceleration", - type: "vec3", - vec3Type: "xyz", - subLabels: [ "x", "y", "z" ], - step: 0.1, - decimals: 4, - unit: "m/s2", - propertyID: "acceleration", - } - ] - }, -]; - -const GROUPS_PER_TYPE = { - None: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Shape: [ 'base', 'shape', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Text: [ 'base', 'text', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Zone: [ 'base', 'zone', 'zone_key_light', 'zone_skybox', 'zone_ambient_light', 'zone_haze', - 'zone_bloom', 'zone_avatar_priority', 'spatial', 'behavior', 'scripts', 'physics' ], - Model: [ 'base', 'model', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Image: [ 'base', 'image', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Web: [ 'base', 'web', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Light: [ 'base', 'light', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Material: [ 'base', 'material', 'spatial', 'behavior', 'scripts', 'physics' ], - ParticleEffect: [ 'base', 'particles', 'particles_emit', 'particles_size', 'particles_color', - 'particles_behavior', 'particles_constraints', 'spatial', 'behavior', 'scripts', 'physics' ], - PolyLine: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - PolyVox: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], - Grid: [ 'base', 'grid', 'spatial', 'behavior', 'scripts', 'physics' ], - Multiple: [ 'base', 'spatial', 'behavior', 'scripts', 'collision', 'physics' ], -}; - -const EDITOR_TIMEOUT_DURATION = 1500; -const DEBOUNCE_TIMEOUT = 125; - -const COLOR_MIN = 0; -const COLOR_MAX = 255; -const COLOR_STEP = 1; - -const MATERIAL_PREFIX_STRING = "mat::"; - -const PENDING_SCRIPT_STATUS = "[ Fetching status ]"; -const NOT_RUNNING_SCRIPT_STATUS = "Not running"; -const ENTITY_SCRIPT_STATUS = { - pending: "Pending", - loading: "Loading", - error_loading_script: "Error loading script", // eslint-disable-line camelcase - error_running_script: "Error running script", // eslint-disable-line camelcase - running: "Running", - unloaded: "Unloaded" -}; - -const ENABLE_DISABLE_SELECTOR = "input, textarea, span, .dropdown dl, .color-picker"; - -const PROPERTY_NAME_DIVISION = { - GROUP: 0, - PROPERTY: 1, - SUB_PROPERTY: 2, -}; - -const RECT_ELEMENTS = { - X_NUMBER: 0, - Y_NUMBER: 1, - WIDTH_NUMBER: 2, - HEIGHT_NUMBER: 3, -}; - -const VECTOR_ELEMENTS = { - X_NUMBER: 0, - Y_NUMBER: 1, - Z_NUMBER: 2, -}; - -const COLOR_ELEMENTS = { - COLOR_PICKER: 0, - RED_NUMBER: 1, - GREEN_NUMBER: 2, - BLUE_NUMBER: 3, -}; - -const TEXTURE_ELEMENTS = { - IMAGE: 0, - TEXT_INPUT: 1, -}; - -const JSON_EDITOR_ROW_DIV_INDEX = 2; - -let elGroups = {}; -let properties = {}; -let propertyRangeRequests = []; -let colorPickers = {}; -let particlePropertyUpdates = {}; -let selectedEntityIDs = new Set(); -let currentSelections = []; -let createAppTooltip = new CreateAppTooltip(); -let currentSpaceMode = PROPERTY_SPACE_MODE.LOCAL; -let zonesList = []; - -function createElementFromHTML(htmlString) { - let elTemplate = document.createElement('template'); - elTemplate.innerHTML = htmlString.trim(); - return elTemplate.content.firstChild; -} - -function isFlagSet(value, flag) { - return (value & flag) === flag; -} - -/** - * GENERAL PROPERTY/GROUP FUNCTIONS - */ - -function getPropertyInputElement(propertyID) { - let property = properties[propertyID]; - switch (property.data.type) { - case 'string': - case 'number': - case 'bool': - case 'dropdown': - case 'textarea': - case 'texture': - return property.elInput; - case 'multipleZonesSelection': - return property.elInput; - case 'number-draggable': - return property.elNumber.elInput; - case 'rect': - return { - x: property.elNumberX.elInput, - y: property.elNumberY.elInput, - width: property.elNumberWidth.elInput, - height: property.elNumberHeight.elInput - }; - case 'vec3': - case 'vec2': - return { x: property.elNumberX.elInput, y: property.elNumberY.elInput, z: property.elNumberZ.elInput }; - case 'color': - return { red: property.elNumberR.elInput, green: property.elNumberG.elInput, blue: property.elNumberB.elInput }; - case 'icon': - return property.elLabel; - case 'dynamic-multiselect': - return property.elDivOptions; - default: - return undefined; - } -} - -function enableChildren(el, selector) { - let elSelectors = el.querySelectorAll(selector); - for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { - elSelectors[selectorIndex].removeAttribute('disabled'); - } -} - -function disableChildren(el, selector) { - let elSelectors = el.querySelectorAll(selector); - for (let selectorIndex = 0; selectorIndex < elSelectors.length; ++selectorIndex) { - elSelectors[selectorIndex].setAttribute('disabled', 'disabled'); - } -} - -function enableProperties() { - enableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); - enableChildren(document, ".colpick"); - enableAllMultipleZoneSelector(); -} - -function disableProperties() { - disableChildren(document.getElementById("properties-list"), ENABLE_DISABLE_SELECTOR); - disableChildren(document, ".colpick"); - for (let pickKey in colorPickers) { - colorPickers[pickKey].colpickHide(); - } - disableAllMultipleZoneSelector(); -} - -function showPropertyElement(propertyID, show) { - setPropertyVisibility(properties[propertyID], show); -} - -function setPropertyVisibility(property, visible) { - property.elContainer.style.display = visible ? null : "none"; -} - -function resetProperties() { - for (let propertyID in properties) { - let property = properties[propertyID]; - let propertyData = property.data; - - switch (propertyData.type) { - case 'number': - case 'string': { - property.elInput.classList.remove('multi-diff'); - if (propertyData.defaultValue !== undefined) { - property.elInput.value = propertyData.defaultValue; - } else { - property.elInput.value = ""; - } - break; - } - case 'bool': { - property.elInput.classList.remove('multi-diff'); - property.elInput.checked = false; - break; - } - case 'number-draggable': { - if (propertyData.defaultValue !== undefined) { - property.elNumber.setValue(propertyData.defaultValue, false); - } else { - property.elNumber.setValue("", false); - } - break; - } - case 'rect': { - property.elNumberX.setValue("", false); - property.elNumberY.setValue("", false); - property.elNumberWidth.setValue("", false); - property.elNumberHeight.setValue("", false); - break; - } - case 'vec3': - case 'vec2': { - property.elNumberX.setValue("", false); - property.elNumberY.setValue("", false); - if (property.elNumberZ !== undefined) { - property.elNumberZ.setValue("", false); - } - break; - } - case 'color': { - property.elColorPicker.style.backgroundColor = "rgb(" + 0 + "," + 0 + "," + 0 + ")"; - property.elNumberR.setValue("", false); - property.elNumberG.setValue("", false); - property.elNumberB.setValue("", false); - break; - } - case 'dropdown': { - property.elInput.classList.remove('multi-diff'); - property.elInput.value = ""; - setDropdownText(property.elInput); - break; - } - case 'textarea': { - property.elInput.classList.remove('multi-diff'); - property.elInput.value = ""; - setTextareaScrolling(property.elInput); - break; - } - case 'multipleZonesSelection': { - property.elInput.classList.remove('multi-diff'); - property.elInput.value = "[]"; - setZonesSelectionData(property.elInput, false); - break; - } - case 'icon': { - property.elSpan.style.display = "none"; - break; - } - case 'texture': { - property.elInput.classList.remove('multi-diff'); - property.elInput.value = ""; - property.elInput.imageLoad(property.elInput.value); - break; - } - case 'dynamic-multiselect': { - resetDynamicMultiselectProperty(property.elDivOptions); - break; - } - } - - let showPropertyRules = properties[propertyID].showPropertyRules; - if (showPropertyRules !== undefined) { - for (let propertyToHide in showPropertyRules) { - showPropertyElement(propertyToHide, false); - } - } - } - - resetServerScriptStatus(); -} - -function resetServerScriptStatus() { - let elServerScriptError = document.getElementById("property-serverScripts-error"); - let elServerScriptStatus = document.getElementById("property-serverScripts-status"); - elServerScriptError.parentElement.style.display = "none"; - elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; -} - -function showGroupsForType(type) { - if (type === "Box" || type === "Sphere") { - showGroupsForTypes(["Shape"]); - showOnTheSamePage(["Shape"]); - return; - } - if (type === "None") { - showGroupsForTypes(["None"]); - return; - } - showGroupsForTypes([type]); - showOnTheSamePage([type]); -} - -function getGroupsForTypes(types) { - return Object.keys(elGroups).filter((groupKey) => { - return types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { - return hasGroup; - }); - }); -} - -function showGroupsForTypes(types) { - Object.entries(elGroups).forEach(([groupKey, elGroup]) => { - if (types.map(type => GROUPS_PER_TYPE[type].includes(groupKey)).every(function (hasGroup) { return hasGroup; })) { - elGroup.style.display = "none"; - if (types !== "None") { - document.getElementById("tab-" + groupKey).style.display = "block"; - } else { - document.getElementById("tab-" + groupKey).style.display = "none"; - } - } else { - elGroup.style.display = "none"; - document.getElementById("tab-" + groupKey).style.display = "none"; - } - }); -} - -function getFirstSelectedID() { - if (selectedEntityIDs.size === 0) { - return null; - } - return selectedEntityIDs.values().next().value; -} - -/** - * Returns true when the user is currently dragging the numeric slider control of the property - * @param propertyName - name of property - * @returns {boolean} currentlyDragging - */ -function isCurrentlyDraggingProperty(propertyName) { - return properties[propertyName] && properties[propertyName].dragging === true; -} - -const SUPPORTED_FALLBACK_TYPES = ['number', 'number-draggable', 'rect', 'vec3', 'vec2', 'color']; - -function getMultiplePropertyValue(originalPropertyName) { - // if this is a compound property name (i.e. animation.running) - // then split it by . up to 3 times to find property value - - let propertyData = null; - if (properties[originalPropertyName] !== undefined) { - propertyData = properties[originalPropertyName].data; - } - - let propertyValues = []; - let splitPropertyName = originalPropertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; - let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; - propertyValues = currentSelections.map(selection => { - let groupProperties = selection.properties[propertyGroupName]; - if (groupProperties === undefined || groupProperties[propertyName] === undefined) { - return undefined; - } - if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) { - let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY]; - return groupProperties[propertyName][subPropertyName]; - } else { - return groupProperties[propertyName]; - } - }); - } else { - propertyValues = currentSelections.map(selection => selection.properties[originalPropertyName]); - } - - if (propertyData !== null && propertyData.fallbackProperty !== undefined && - SUPPORTED_FALLBACK_TYPES.includes(propertyData.type)) { - - let fallbackMultiValue = null; - - for (let i = 0; i < propertyValues.length; ++i) { - let isPropertyNotNumber = false; - let propertyValue = propertyValues[i]; - if (propertyValue === undefined) { - continue; - } - switch (propertyData.type) { - case 'number': - case 'number-draggable': - isPropertyNotNumber = isNaN(propertyValue) || propertyValue === null; - break; - case 'rect': - case 'vec3': - case 'vec2': - isPropertyNotNumber = isNaN(propertyValue.x) || propertyValue.x === null; - break; - case 'color': - isPropertyNotNumber = isNaN(propertyValue.red) || propertyValue.red === null; - break; - } - if (isPropertyNotNumber) { - if (fallbackMultiValue === null) { - fallbackMultiValue = getMultiplePropertyValue(propertyData.fallbackProperty); - } - propertyValues[i] = fallbackMultiValue.values[i]; - } - } - } - - const firstValue = propertyValues[0]; - const isMultiDiffValue = !propertyValues.every((x) => deepEqual(firstValue, x)); - - if (isMultiDiffValue) { - return { - value: undefined, - values: propertyValues, - isMultiDiffValue: true - } - } - - return { - value: propertyValues[0], - values: propertyValues, - isMultiDiffValue: false - }; -} - -/** - * Retrieve more detailed info for differing Numeric MultiplePropertyValue - * @param multiplePropertyValue - input multiplePropertyValue - * @param propertyData - * @returns {{keys: *[], propertyComponentDiff, averagePerPropertyComponent}} - */ -function getDetailedNumberMPVDiff(multiplePropertyValue, propertyData) { - let detailedValues = {}; - // Fixed numbers can't be easily averaged since they're strings, so lets keep an array of unmodified numbers - let unmodifiedValues = {}; - const DEFAULT_KEY = 0; - let uniqueKeys = new Set([]); - multiplePropertyValue.values.forEach(function(propertyValue) { - if (typeof propertyValue === "object") { - Object.entries(propertyValue).forEach(function([key, value]) { - if (!uniqueKeys.has(key)) { - uniqueKeys.add(key); - detailedValues[key] = []; - unmodifiedValues[key] = []; - } - detailedValues[key].push(applyInputNumberPropertyModifiers(value, propertyData)); - unmodifiedValues[key].push(value); - }); - } else { - if (!uniqueKeys.has(DEFAULT_KEY)) { - uniqueKeys.add(DEFAULT_KEY); - detailedValues[DEFAULT_KEY] = []; - unmodifiedValues[DEFAULT_KEY] = []; - } - detailedValues[DEFAULT_KEY].push(applyInputNumberPropertyModifiers(propertyValue, propertyData)); - unmodifiedValues[DEFAULT_KEY].push(propertyValue); - } - }); - let keys = [...uniqueKeys]; - - let propertyComponentDiff = {}; - Object.entries(detailedValues).forEach(function([key, value]) { - propertyComponentDiff[key] = [...new Set(value)].length > 1; - }); - - let averagePerPropertyComponent = {}; - Object.entries(unmodifiedValues).forEach(function([key, value]) { - let average = value.reduce((a, b) => a + b) / value.length; - averagePerPropertyComponent[key] = applyInputNumberPropertyModifiers(average, propertyData); - }); - - return { - keys, - propertyComponentDiff, - averagePerPropertyComponent, - }; -} - -function getDetailedSubPropertyMPVDiff(multiplePropertyValue, subPropertyName) { - let isChecked = false; - let checkedValues = multiplePropertyValue.values.map((value) => value.split(",").includes(subPropertyName)); - let isMultiDiff = !checkedValues.every(value => value === checkedValues[0]); - if (!isMultiDiff) { - isChecked = checkedValues[0]; - } - return { - isChecked, - isMultiDiff - } -} - -function updateVisibleSpaceModeProperties() { - for (let propertyID in properties) { - if (properties.hasOwnProperty(propertyID)) { - let property = properties[propertyID]; - let propertySpaceMode = property.spaceMode; - let elProperty = properties[propertyID].elContainer; - if (propertySpaceMode !== PROPERTY_SPACE_MODE.ALL && propertySpaceMode !== currentSpaceMode) { - elProperty.classList.add('spacemode-hidden'); - } else { - elProperty.classList.remove('spacemode-hidden'); - } - } - } -} - -/** - * PROPERTY UPDATE FUNCTIONS - */ - -function createPropertyUpdateObject(originalPropertyName, propertyValue) { - let propertyUpdate = {}; - // if this is a compound property name (i.e. animation.running) then split it by . up to 3 times - let splitPropertyName = originalPropertyName.split('.'); - if (splitPropertyName.length > 1) { - let propertyGroupName = splitPropertyName[PROPERTY_NAME_DIVISION.GROUP]; - let propertyName = splitPropertyName[PROPERTY_NAME_DIVISION.PROPERTY]; - propertyUpdate[propertyGroupName] = {}; - if (splitPropertyName.length === PROPERTY_NAME_DIVISION.SUB_PROPERTY + 1) { - let subPropertyName = splitPropertyName[PROPERTY_NAME_DIVISION.SUB_PROPERTY]; - propertyUpdate[propertyGroupName][propertyName] = {}; - propertyUpdate[propertyGroupName][propertyName][subPropertyName] = propertyValue; - } else { - propertyUpdate[propertyGroupName][propertyName] = propertyValue; - } - } else { - propertyUpdate[originalPropertyName] = propertyValue; - } - return propertyUpdate; -} - -function updateProperty(originalPropertyName, propertyValue, isParticleProperty) { - let propertyUpdate = createPropertyUpdateObject(originalPropertyName, propertyValue); - - // queue up particle property changes with the debounced sync to avoid - // causing particle emitting to reset excessively with each value change - if (isParticleProperty) { - Object.keys(propertyUpdate).forEach(function (propertyUpdateKey) { - particlePropertyUpdates[propertyUpdateKey] = propertyUpdate[propertyUpdateKey]; - }); - particleSyncDebounce(); - } else { - // only update the entity property value itself if in the middle of dragging - // prevent undo command push, saving new property values, and property update - // callback until drag is complete (additional update sent via dragEnd callback) - let onlyUpdateEntity = isCurrentlyDraggingProperty(originalPropertyName); - updateProperties(propertyUpdate, onlyUpdateEntity); - } -} - -let particleSyncDebounce = _.debounce(function () { - updateProperties(particlePropertyUpdates); - particlePropertyUpdates = {}; -}, DEBOUNCE_TIMEOUT); - -function updateProperties(propertiesToUpdate, onlyUpdateEntity) { - if (onlyUpdateEntity === undefined) { - onlyUpdateEntity = false; - } - EventBridge.emitWebEvent(JSON.stringify({ - ids: [...selectedEntityIDs], - type: "update", - properties: propertiesToUpdate, - onlyUpdateEntities: onlyUpdateEntity - })); -} - -function updateMultiDiffProperties(propertiesMapToUpdate, onlyUpdateEntity) { - if (onlyUpdateEntity === undefined) { - onlyUpdateEntity = false; - } - EventBridge.emitWebEvent(JSON.stringify({ - type: "update", - propertiesMap: propertiesMapToUpdate, - onlyUpdateEntities: onlyUpdateEntity - })); -} - -function createEmitTextPropertyUpdateFunction(property) { - return function() { - property.elInput.classList.remove('multi-diff'); - updateProperty(property.name, this.value, property.isParticleProperty); - }; -} - -function createEmitCheckedPropertyUpdateFunction(property) { - return function() { - updateProperty(property.name, property.data.inverse ? !this.checked : this.checked, property.isParticleProperty); - }; -} - -function createDragStartFunction(property) { - return function() { - property.dragging = true; - }; -} - -function createDragEndFunction(property) { - return function() { - property.dragging = false; - - if (this.multiDiffModeEnabled) { - let propertyMultiValue = getMultiplePropertyValue(property.name); - let updateObjects = []; - const selectedEntityIDsArray = [...selectedEntityIDs]; - - for (let i = 0; i < selectedEntityIDsArray.length; ++i) { - let entityID = selectedEntityIDsArray[i]; - updateObjects.push({ - entityIDs: [entityID], - properties: createPropertyUpdateObject(property.name, propertyMultiValue.values[i]), - }); - } - - // send a full updateMultiDiff post-dragging to count as an action in the undo stack - updateMultiDiffProperties(updateObjects); - } else { - // send an additional update post-dragging to consider whole property change from dragStart to dragEnd to be 1 action - this.valueChangeFunction(); - } - }; -} - -function createEmitNumberPropertyUpdateFunction(property) { - return function() { - let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data)); - updateProperty(property.name, value, property.isParticleProperty); - }; -} - -function createEmitNumberPropertyComponentUpdateFunction(property, propertyComponent) { - return function() { - let propertyMultiValue = getMultiplePropertyValue(property.name); - let value = parseFloat(applyOutputNumberPropertyModifiers(parseFloat(this.value), property.data)); - - if (propertyMultiValue.isMultiDiffValue) { - let updateObjects = []; - const selectedEntityIDsArray = [...selectedEntityIDs]; - - for (let i = 0; i < selectedEntityIDsArray.length; ++i) { - let entityID = selectedEntityIDsArray[i]; - - let propertyObject = propertyMultiValue.values[i]; - propertyObject[propertyComponent] = value; - - let updateObject = createPropertyUpdateObject(property.name, propertyObject); - updateObjects.push({ - entityIDs: [entityID], - properties: updateObject, - }); - - mergeDeep(currentSelections[i].properties, updateObject); - } - - // only update the entity property value itself if in the middle of dragging - // prevent undo command push, saving new property values, and property update - // callback until drag is complete (additional update sent via dragEnd callback) - let onlyUpdateEntity = isCurrentlyDraggingProperty(property.name); - updateMultiDiffProperties(updateObjects, onlyUpdateEntity); - } else { - let propertyValue = propertyMultiValue.value; - propertyValue[propertyComponent] = value; - updateProperty(property.name, propertyValue, property.isParticleProperty); - } - }; -} - -function createEmitColorPropertyUpdateFunction(property) { - return function() { - emitColorPropertyUpdate(property.name, property.elNumberR.elInput.value, property.elNumberG.elInput.value, - property.elNumberB.elInput.value, property.isParticleProperty); - }; -} - -function emitColorPropertyUpdate(propertyName, red, green, blue, isParticleProperty) { - let newValue = { - red: red, - green: green, - blue: blue - }; - updateProperty(propertyName, newValue, isParticleProperty); -} - -function toggleBooleanCSV(inputCSV, property, enable) { - let values = inputCSV.split(","); - if (enable && !values.includes(property)) { - values.push(property); - } else if (!enable && values.includes(property)) { - values = values.filter(value => value !== property); - } - return values.join(","); -} - -function updateCheckedSubProperty(propertyName, propertyMultiValue, subPropertyElement, subPropertyString, isParticleProperty) { - if (propertyMultiValue.isMultiDiffValue) { - let updateObjects = []; - const selectedEntityIDsArray = [...selectedEntityIDs]; - - for (let i = 0; i < selectedEntityIDsArray.length; ++i) { - let newValue = toggleBooleanCSV(propertyMultiValue.values[i], subPropertyString, subPropertyElement.checked); - updateObjects.push({ - entityIDs: [selectedEntityIDsArray[i]], - properties: createPropertyUpdateObject(propertyName, newValue), - }); - } - - updateMultiDiffProperties(updateObjects); - } else { - updateProperty(propertyName, toggleBooleanCSV(propertyMultiValue.value, subPropertyString, subPropertyElement.checked), - isParticleProperty); - } -} - -/** - * PROPERTY ELEMENT CREATION FUNCTIONS - */ - -function createStringProperty(property, elProperty) { - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className = "text"; - - let elInput = createElementFromHTML(` - - `); - - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); - if (propertyData.onChange !== undefined) { - elInput.addEventListener('change', propertyData.onChange); - } - - - let elMultiDiff = document.createElement('span'); - elMultiDiff.className = "multi-diff"; - - elProperty.appendChild(elInput); - elProperty.appendChild(elMultiDiff); - - if (propertyData.buttons !== undefined) { - addButtons(elProperty, elementID, propertyData.buttons, false); - } - - return elInput; -} - -function createBoolProperty(property, elProperty) { - let propertyName = property.name; - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className = "checkbox"; - - if (propertyData.glyph !== undefined) { - let elSpan = document.createElement('span'); - elSpan.innerHTML = propertyData.glyph; - elSpan.className = 'icon'; - elProperty.appendChild(elSpan); - } - - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementID); - elInput.setAttribute("type", "checkbox"); - - elProperty.appendChild(elInput); - elProperty.appendChild(createElementFromHTML(``)); - - let subPropertyOf = propertyData.subPropertyOf; - if (subPropertyOf !== undefined) { - elInput.addEventListener('change', function() { - let subPropertyMultiValue = getMultiplePropertyValue(subPropertyOf); - - updateCheckedSubProperty(subPropertyOf, - subPropertyMultiValue, - elInput, propertyName, property.isParticleProperty); - }); - } else { - elInput.addEventListener('change', createEmitCheckedPropertyUpdateFunction(property)); - } - - return elInput; -} - -function createNumberProperty(property, elProperty) { - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className = "text"; - - let elInput = createElementFromHTML(` - - `); - - if (propertyData.min !== undefined) { - elInput.setAttribute("min", propertyData.min); - } - if (propertyData.max !== undefined) { - elInput.setAttribute("max", propertyData.max); - } - if (propertyData.step !== undefined) { - elInput.setAttribute("step", propertyData.step); - } - if (propertyData.defaultValue !== undefined) { - elInput.value = propertyData.defaultValue; - } - - elInput.addEventListener('change', createEmitNumberPropertyUpdateFunction(property)); - - let elMultiDiff = document.createElement('span'); - elMultiDiff.className = "multi-diff"; - - elProperty.appendChild(elInput); - elProperty.appendChild(elMultiDiff); - - if (propertyData.buttons !== undefined) { - addButtons(elProperty, elementID, propertyData.buttons, false); - } - - return elInput; -} - -function updateNumberMinMax(property) { - let elInput = property.elInput; - let min = property.data.min; - let max = property.data.max; - if (min !== undefined) { - elInput.setAttribute("min", min); - } - if (max !== undefined) { - elInput.setAttribute("max", max); - } -} - -/** - * - * @param {object} property - property update on step - * @param {string} [propertyComponent] - propertyComponent to update on step (e.g. enter 'x' to just update position.x) - * @returns {Function} - */ -function createMultiDiffStepFunction(property, propertyComponent) { - return function(step, shouldAddToUndoHistory) { - if (shouldAddToUndoHistory === undefined) { - shouldAddToUndoHistory = false; - } - - let propertyMultiValue = getMultiplePropertyValue(property.name); - if (!propertyMultiValue.isMultiDiffValue) { - console.log("setMultiDiffStepFunction is only supposed to be called in MultiDiff mode."); - return; - } - - let multiplier = property.data.multiplier !== undefined ? property.data.multiplier : 1; - - let applyDelta = step * multiplier; - - if (selectedEntityIDs.size !== propertyMultiValue.values.length) { - console.log("selectedEntityIDs and propertyMultiValue got out of sync."); - return; - } - let updateObjects = []; - const selectedEntityIDsArray = [...selectedEntityIDs]; - - for (let i = 0; i < selectedEntityIDsArray.length; ++i) { - let entityID = selectedEntityIDsArray[i]; - - let updatedValue; - if (propertyComponent !== undefined) { - let objectToUpdate = propertyMultiValue.values[i]; - objectToUpdate[propertyComponent] += applyDelta; - updatedValue = objectToUpdate; - } else { - updatedValue = propertyMultiValue.values[i] + applyDelta; - } - let propertiesUpdate = createPropertyUpdateObject(property.name, updatedValue); - updateObjects.push({ - entityIDs: [entityID], - properties: propertiesUpdate - }); - // We need to store these so that we can send a full update on the dragEnd - mergeDeep(currentSelections[i].properties, propertiesUpdate); - } - - updateMultiDiffProperties(updateObjects, !shouldAddToUndoHistory); - } -} - -function createNumberDraggableProperty(property, elProperty) { - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className += " draggable-number-container"; - - let dragStartFunction = createDragStartFunction(property); - let dragEndFunction = createDragEndFunction(property); - let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, - propertyData.decimals, dragStartFunction, dragEndFunction); - - let defaultValue = propertyData.defaultValue; - if (defaultValue !== undefined) { - elDraggableNumber.elInput.value = defaultValue; - } - - let valueChangeFunction = createEmitNumberPropertyUpdateFunction(property); - elDraggableNumber.setValueChangeFunction(valueChangeFunction); - - elDraggableNumber.setMultiDiffStepFunction(createMultiDiffStepFunction(property)); - - elDraggableNumber.elInput.setAttribute("id", elementID); - elProperty.appendChild(elDraggableNumber.elDiv); - - if (propertyData.buttons !== undefined) { - addButtons(elDraggableNumber.elDiv, elementID, propertyData.buttons, false); - } - - return elDraggableNumber; -} - -function updateNumberDraggableMinMax(property) { - let propertyData = property.data; - property.elNumber.updateMinMax(propertyData.min, propertyData.max); -} - -function createRectProperty(property, elProperty) { - let propertyData = property.data; - - elProperty.className = "rect"; - - let elXYRow = document.createElement('div'); - elXYRow.className = "rect-row fstuple"; - elProperty.appendChild(elXYRow); - - let elWidthHeightRow = document.createElement('div'); - elWidthHeightRow.className = "rect-row fstuple"; - elProperty.appendChild(elWidthHeightRow); - - - let elNumberX = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.X_NUMBER]); - let elNumberY = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.Y_NUMBER]); - let elNumberWidth = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.WIDTH_NUMBER]); - let elNumberHeight = createTupleNumberInput(property, propertyData.subLabels[RECT_ELEMENTS.HEIGHT_NUMBER]); - - elXYRow.appendChild(elNumberX.elDiv); - elXYRow.appendChild(elNumberY.elDiv); - elWidthHeightRow.appendChild(elNumberWidth.elDiv); - elWidthHeightRow.appendChild(elNumberHeight.elDiv); - - elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); - elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); - elNumberWidth.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'width')); - elNumberHeight.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'height')); - - elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); - elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); - elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'width')); - elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'height')); - - let elResult = []; - elResult[RECT_ELEMENTS.X_NUMBER] = elNumberX; - elResult[RECT_ELEMENTS.Y_NUMBER] = elNumberY; - elResult[RECT_ELEMENTS.WIDTH_NUMBER] = elNumberWidth; - elResult[RECT_ELEMENTS.HEIGHT_NUMBER] = elNumberHeight; - return elResult; -} - -function updateRectMinMax(property) { - let min = property.data.min; - let max = property.data.max; - property.elNumberX.updateMinMax(min, max); - property.elNumberY.updateMinMax(min, max); - property.elNumberWidth.updateMinMax(min, max); - property.elNumberHeight.updateMinMax(min, max); -} - -function createVec3Property(property, elProperty) { - let propertyData = property.data; - - elProperty.className = propertyData.vec3Type + " fstuple"; - - let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); - let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); - let elNumberZ = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Z_NUMBER]); - elProperty.appendChild(elNumberX.elDiv); - elProperty.appendChild(elNumberY.elDiv); - elProperty.appendChild(elNumberZ.elDiv); - - elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); - elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); - elNumberZ.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'z')); - - elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); - elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); - elNumberZ.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'z')); - - let elResult = []; - elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX; - elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY; - elResult[VECTOR_ELEMENTS.Z_NUMBER] = elNumberZ; - return elResult; -} - -function createVec2Property(property, elProperty) { - let propertyData = property.data; - - elProperty.className = propertyData.vec2Type + " fstuple"; - - let elTuple = document.createElement('div'); - elTuple.className = "tuple"; - - elProperty.appendChild(elTuple); - - let elNumberX = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.X_NUMBER]); - let elNumberY = createTupleNumberInput(property, propertyData.subLabels[VECTOR_ELEMENTS.Y_NUMBER]); - elProperty.appendChild(elNumberX.elDiv); - elProperty.appendChild(elNumberY.elDiv); - - elNumberX.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'x')); - elNumberY.setValueChangeFunction(createEmitNumberPropertyComponentUpdateFunction(property, 'y')); - - elNumberX.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'x')); - elNumberY.setMultiDiffStepFunction(createMultiDiffStepFunction(property, 'y')); - - let elResult = []; - elResult[VECTOR_ELEMENTS.X_NUMBER] = elNumberX; - elResult[VECTOR_ELEMENTS.Y_NUMBER] = elNumberY; - return elResult; -} - -function updateVectorMinMax(property) { - let min = property.data.min; - let max = property.data.max; - property.elNumberX.updateMinMax(min, max); - property.elNumberY.updateMinMax(min, max); - if (property.elNumberZ) { - property.elNumberZ.updateMinMax(min, max); - } -} - -function createColorProperty(property, elProperty) { - let propertyName = property.name; - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className += " rgb fstuple"; - - let elColorPicker = document.createElement('div'); - elColorPicker.className = "color-picker"; - elColorPicker.setAttribute("id", elementID); - - let elTuple = document.createElement('div'); - elTuple.className = "tuple"; - - elProperty.appendChild(elColorPicker); - elProperty.appendChild(elTuple); - - if (propertyData.min === undefined) { - propertyData.min = COLOR_MIN; - } - if (propertyData.max === undefined) { - propertyData.max = COLOR_MAX; - } - if (propertyData.step === undefined) { - propertyData.step = COLOR_STEP; - } - - let elNumberR = createTupleNumberInput(property, "red"); - let elNumberG = createTupleNumberInput(property, "green"); - let elNumberB = createTupleNumberInput(property, "blue"); - elTuple.appendChild(elNumberR.elDiv); - elTuple.appendChild(elNumberG.elDiv); - elTuple.appendChild(elNumberB.elDiv); - - let valueChangeFunction = createEmitColorPropertyUpdateFunction(property); - elNumberR.setValueChangeFunction(valueChangeFunction); - elNumberG.setValueChangeFunction(valueChangeFunction); - elNumberB.setValueChangeFunction(valueChangeFunction); - - let colorPickerID = "#" + elementID; - colorPickers[colorPickerID] = $(colorPickerID).colpick({ - colorScheme: 'dark', - layout: 'rgbhex', - color: '000000', - submit: false, // We don't want to have a submission button - onShow: function(colpick) { - // The original color preview within the picker needs to be updated on show because - // prior to the picker being shown we don't have access to the selections' starting color. - colorPickers[colorPickerID].colpickSetColor({ - "r": elNumberR.elInput.value, - "g": elNumberG.elInput.value, - "b": elNumberB.elInput.value - }); - - // Set the color picker active after setting the color, otherwise an update will be sent on open. - $(colorPickerID).attr('active', 'true'); - }, - onHide: function(colpick) { - $(colorPickerID).attr('active', 'false'); - }, - onChange: function(hsb, hex, rgb, el) { - $(el).css('background-color', '#' + hex); - if ($(colorPickerID).attr('active') === 'true') { - emitColorPropertyUpdate(propertyName, rgb.r, rgb.g, rgb.b); - } - } - }); - - let elResult = []; - elResult[COLOR_ELEMENTS.COLOR_PICKER] = elColorPicker; - elResult[COLOR_ELEMENTS.RED_NUMBER] = elNumberR; - elResult[COLOR_ELEMENTS.GREEN_NUMBER] = elNumberG; - elResult[COLOR_ELEMENTS.BLUE_NUMBER] = elNumberB; - return elResult; -} - -function createDropdownProperty(property, propertyID, elProperty) { - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className = "dropdown"; - - let elInput = document.createElement('select'); - elInput.setAttribute("id", elementID); - elInput.setAttribute("propertyID", propertyID); - - for (let optionKey in propertyData.options) { - let option = document.createElement('option'); - option.value = optionKey; - option.text = propertyData.options[optionKey]; - elInput.add(option); - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); - - elProperty.appendChild(elInput); - - return elInput; -} - -function createTextareaProperty(property, elProperty) { - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className = "textarea"; - - let elInput = document.createElement('textarea'); - elInput.setAttribute("id", elementID); - if (propertyData.readOnly) { - elInput.readOnly = true; - } - - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); - - let elMultiDiff = document.createElement('span'); - elMultiDiff.className = "multi-diff"; - - elProperty.appendChild(elInput); - elProperty.appendChild(elMultiDiff); - - if (propertyData.buttons !== undefined) { - addButtons(elProperty, elementID, propertyData.buttons, true); - } - - return elInput; -} - -function createIconProperty(property, elProperty) { - let elementID = property.elementID; - - elProperty.className = "value"; - - let elSpan = document.createElement('span'); - elSpan.setAttribute("id", elementID + "-icon"); - elSpan.className = 'icon'; - - elProperty.appendChild(elSpan); - - return elSpan; -} - -function createTextureProperty(property, elProperty) { - let elementID = property.elementID; - - elProperty.className = "texture"; - - let elDiv = document.createElement("div"); - let elImage = document.createElement("img"); - elDiv.className = "texture-image no-texture"; - elDiv.appendChild(elImage); - - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementID); - elInput.setAttribute("type", "text"); - - let imageLoad = function(url) { - elDiv.style.display = null; - if (url.slice(0, 5).toLowerCase() === "atp:/") { - elImage.src = ""; - elImage.style.display = "none"; - elDiv.classList.remove("with-texture"); - elDiv.classList.remove("no-texture"); - elDiv.classList.add("no-preview"); - } else if (url.length > 0) { - elDiv.classList.remove("no-texture"); - elDiv.classList.remove("no-preview"); - elDiv.classList.add("with-texture"); - elImage.src = url; - elImage.style.display = "block"; - } else { - elImage.src = ""; - elImage.style.display = "none"; - elDiv.classList.remove("with-texture"); - elDiv.classList.remove("no-preview"); - elDiv.classList.add("no-texture"); - } - }; - elInput.imageLoad = imageLoad; - elInput.setMultipleValues = function() { - elDiv.style.display = "none"; - }; - elInput.addEventListener('change', createEmitTextPropertyUpdateFunction(property)); - elInput.addEventListener('change', function(ev) { - imageLoad(ev.target.value); - }); - - elProperty.appendChild(elInput); - let elMultiDiff = document.createElement('span'); - elMultiDiff.className = "multi-diff"; - elProperty.appendChild(elMultiDiff); - elProperty.appendChild(elDiv); - - let elResult = []; - elResult[TEXTURE_ELEMENTS.IMAGE] = elImage; - elResult[TEXTURE_ELEMENTS.TEXT_INPUT] = elInput; - return elResult; -} - -function createButtonsProperty(property, elProperty) { - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className = "text"; - - if (propertyData.buttons !== undefined) { - addButtons(elProperty, elementID, propertyData.buttons, false); - } - - return elProperty; -} - -function createDynamicMultiselectProperty(property, elProperty) { - let elementID = property.elementID; - let propertyData = property.data; - - elProperty.className = "dynamic-multiselect"; - - let elDivOptions = document.createElement('div'); - elDivOptions.setAttribute("id", elementID + "-options"); - elDivOptions.style = "overflow-y:scroll;max-height:160px;"; - - let elDivButtons = document.createElement('div'); - elDivButtons.setAttribute("id", elDivOptions.getAttribute("id") + "-buttons"); - - let elLabel = document.createElement('label'); - elLabel.innerText = "No Options"; - elDivOptions.appendChild(elLabel); - - let buttons = [ { id: "selectAll", label: "Select All", className: "black", onClick: selectAllMaterialTarget }, - { id: "clearAll", label: "Clear All", className: "black", onClick: clearAllMaterialTarget } ]; - addButtons(elDivButtons, elementID, buttons, false); - - elProperty.appendChild(elDivOptions); - elProperty.appendChild(elDivButtons); - - return elDivOptions; -} - -function resetDynamicMultiselectProperty(elDivOptions) { - let elInputs = elDivOptions.getElementsByTagName("input"); - while (elInputs.length > 0) { - let elDivOption = elInputs[0].parentNode; - elDivOption.parentNode.removeChild(elDivOption); - } - elDivOptions.firstChild.style.display = null; // show "No Options" text - elDivOptions.parentNode.lastChild.style.display = "none"; // hide Select/Clear all buttons -} - -function createTupleNumberInput(property, subLabel) { - let propertyElementID = property.elementID; - let propertyData = property.data; - let elementID = propertyElementID + "-" + subLabel.toLowerCase(); - - let elLabel = document.createElement('label'); - elLabel.className = "sublabel " + subLabel; - elLabel.innerText = subLabel[0].toUpperCase() + subLabel.slice(1); - elLabel.setAttribute("for", elementID); - elLabel.style.visibility = "visible"; - - let dragStartFunction = createDragStartFunction(property); - let dragEndFunction = createDragEndFunction(property); - let elDraggableNumber = new DraggableNumber(propertyData.min, propertyData.max, propertyData.step, - propertyData.decimals, dragStartFunction, dragEndFunction); - elDraggableNumber.elInput.setAttribute("id", elementID); - elDraggableNumber.elDiv.className += " fstuple"; - elDraggableNumber.elDiv.insertBefore(elLabel, elDraggableNumber.elLeftArrow); - - return elDraggableNumber; -} - -function addButtons(elProperty, propertyID, buttons, newRow) { - let elDiv = document.createElement('div'); - elDiv.className = "row"; - - buttons.forEach(function(button) { - let elButton = document.createElement('input'); - elButton.className = button.className; - elButton.setAttribute("type", "button"); - elButton.setAttribute("id", propertyID + "-button-" + button.id); - elButton.setAttribute("value", button.label); - elButton.addEventListener("click", button.onClick); - if (newRow) { - elDiv.appendChild(elButton); - } else { - elProperty.appendChild(elButton); - } - }); - - if (newRow) { - elProperty.appendChild(document.createElement('br')); - elProperty.appendChild(elDiv); - } -} - -function createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty) { - let property = { - data: propertyData, - elementID: propertyElementID, - name: propertyName, - elProperty: elProperty, - }; - let propertyType = propertyData.type; - - switch (propertyType) { - case 'string': { - property.elInput = createStringProperty(property, elProperty); - break; - } - case 'bool': { - property.elInput = createBoolProperty(property, elProperty); - break; - } - case 'number': { - property.elInput = createNumberProperty(property, elProperty); - break; - } - case 'number-draggable': { - property.elNumber = createNumberDraggableProperty(property, elProperty); - break; - } - case 'rect': { - let elRect = createRectProperty(property, elProperty); - property.elNumberX = elRect[RECT_ELEMENTS.X_NUMBER]; - property.elNumberY = elRect[RECT_ELEMENTS.Y_NUMBER]; - property.elNumberWidth = elRect[RECT_ELEMENTS.WIDTH_NUMBER]; - property.elNumberHeight = elRect[RECT_ELEMENTS.HEIGHT_NUMBER]; - break; - } - case 'vec3': { - let elVec3 = createVec3Property(property, elProperty); - property.elNumberX = elVec3[VECTOR_ELEMENTS.X_NUMBER]; - property.elNumberY = elVec3[VECTOR_ELEMENTS.Y_NUMBER]; - property.elNumberZ = elVec3[VECTOR_ELEMENTS.Z_NUMBER]; - break; - } - case 'vec2': { - let elVec2 = createVec2Property(property, elProperty); - property.elNumberX = elVec2[VECTOR_ELEMENTS.X_NUMBER]; - property.elNumberY = elVec2[VECTOR_ELEMENTS.Y_NUMBER]; - break; - } - case 'color': { - let elColor = createColorProperty(property, elProperty); - property.elColorPicker = elColor[COLOR_ELEMENTS.COLOR_PICKER]; - property.elNumberR = elColor[COLOR_ELEMENTS.RED_NUMBER]; - property.elNumberG = elColor[COLOR_ELEMENTS.GREEN_NUMBER]; - property.elNumberB = elColor[COLOR_ELEMENTS.BLUE_NUMBER]; - break; - } - case 'dropdown': { - property.elInput = createDropdownProperty(property, propertyID, elProperty); - break; - } - case 'textarea': { - property.elInput = createTextareaProperty(property, elProperty); - break; - } - case 'multipleZonesSelection': { - property.elInput = createZonesSelection(property, elProperty); - break; - } - case 'icon': { - property.elSpan = createIconProperty(property, elProperty); - break; - } - case 'texture': { - let elTexture = createTextureProperty(property, elProperty); - property.elImage = elTexture[TEXTURE_ELEMENTS.IMAGE]; - property.elInput = elTexture[TEXTURE_ELEMENTS.TEXT_INPUT]; - break; - } - case 'buttons': { - property.elProperty = createButtonsProperty(property, elProperty); - break; - } - case 'dynamic-multiselect': { - property.elDivOptions = createDynamicMultiselectProperty(property, elProperty); - break; - } - case 'placeholder': - case 'sub-header': { - break; - } - default: { - console.log("EntityProperties - Unknown property type " + - propertyType + " set to property " + propertyID); - break; - } - } - - return property; -} - - -/** - * PROPERTY-SPECIFIC CALLBACKS - */ - -function parentIDChanged() { - if (currentSelections.length === 1 && currentSelections[0].properties.type === "Material") { - requestMaterialTarget(); - } -} - - -/** - * BUTTON CALLBACKS - */ - -function rescaleDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "rescaleDimensions", - percentage: parseFloat(document.getElementById("property-scale").value) - })); -} - -function moveSelectionToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveSelectionToGrid" - })); -} - -function moveAllToGrid() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "moveAllToGrid" - })); -} - -function resetToNaturalDimensions() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "resetToNaturalDimensions" - })); -} - -function reloadScripts() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadClientScripts" - })); -} - -function reloadServerScripts() { - // invalidate the current status (so that same-same updates can still be observed visually) - document.getElementById("property-serverScripts-status").innerText = PENDING_SCRIPT_STATUS; - EventBridge.emitWebEvent(JSON.stringify({ - type: "action", - action: "reloadServerScripts" - })); -} - -function copySkyboxURLToAmbientURL() { - let skyboxURL = getPropertyInputElement("skybox.url").value; - getPropertyInputElement("ambientLight.ambientURL").value = skyboxURL; - updateProperty("ambientLight.ambientURL", skyboxURL, false); -} - - -/** - * USER DATA FUNCTIONS - */ - -function clearUserData() { - let elUserData = getPropertyInputElement("userData"); - deleteJSONEditor(); - elUserData.value = ""; - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - updateProperty('userData', elUserData.value, false); -} - -function newJSONEditor() { - getPropertyInputElement("userData").classList.remove('multi-diff'); - deleteJSONEditor(); - createJSONEditor(); - let data = {}; - setEditorJSON(data); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - showSaveUserDataButton(); -} - -/** - * @param {Set.} [entityIDsToUpdate] Entity IDs to update userData for. - */ -function saveUserData(entityIDsToUpdate) { - saveJSONUserData(true, entityIDsToUpdate); -} - -function setJSONError(property, isError) { - $("#property-"+ property + "-editor").toggleClass('error', isError); - let $propertyUserDataEditorStatus = $("#property-"+ property + "-editorStatus"); - $propertyUserDataEditorStatus.css('display', isError ? 'block' : 'none'); - $propertyUserDataEditorStatus.text(isError ? 'Invalid JSON code - look for red X in your code' : ''); -} - -/** - * @param {boolean} noUpdate - don't update the UI, but do send a property update. - * @param {Set.} [entityIDsToUpdate] - Entity IDs to update userData for. - */ -function setUserDataFromEditor(noUpdate, entityIDsToUpdate) { - let errorFound = false; - try { - editor.get(); - } catch (e) { - errorFound = true; - } - - setJSONError('userData', errorFound); - - if (errorFound) { - return; - } - - let text = editor.getText(); - if (noUpdate) { - EventBridge.emitWebEvent( - JSON.stringify({ - ids: [...entityIDsToUpdate], - type: "saveUserData", - properties: { - userData: text - } - }) - ); - } else { - updateProperty('userData', text, false); - } -} - -let editor = null; - -function createJSONEditor() { - let container = document.getElementById("property-userData-editor"); - let options = { - search: false, - mode: 'tree', - modes: ['code', 'tree'], - name: 'userData', - onError: function(e) { - alert('JSON editor:' + e); - }, - onChange: function() { - let currentJSONString = editor.getText(); - - if (currentJSONString === '{"":""}') { - return; - } - $('#property-userData-button-save').attr('disabled', false); - } - }; - editor = new JSONEditor(container, options); -} - -function showSaveUserDataButton() { - $('#property-userData-button-save').show(); -} - -function hideSaveUserDataButton() { - $('#property-userData-button-save').hide(); -} - -function disableSaveUserDataButton() { - $('#property-userData-button-save').attr('disabled', true); -} - -function showNewJSONEditorButton() { - $('#property-userData-button-edit').show(); -} - -function hideNewJSONEditorButton() { - $('#property-userData-button-edit').hide(); -} - -function showUserDataTextArea() { - $('#property-userData').show(); -} - -function hideUserDataTextArea() { - $('#property-userData').hide(); -} - -function hideUserDataSaved() { - $('#property-userData-saved').hide(); -} - -function setEditorJSON(json) { - editor.set(json); - if (editor.hasOwnProperty('expandAll')) { - editor.expandAll(); - } -} - -function deleteJSONEditor() { - if (editor !== null) { - setJSONError('userData', false); - editor.destroy(); - editor = null; - } -} - -let savedJSONTimer = null; - -/** - * @param {boolean} noUpdate - don't update the UI, but do send a property update. - * @param {Set.} [entityIDsToUpdate] Entity IDs to update userData for - */ -function saveJSONUserData(noUpdate, entityIDsToUpdate) { - setUserDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); - $('#property-userData-saved').show(); - $('#property-userData-button-save').attr('disabled', true); - if (savedJSONTimer !== null) { - clearTimeout(savedJSONTimer); - } - savedJSONTimer = setTimeout(function() { - hideUserDataSaved(); - }, EDITOR_TIMEOUT_DURATION); -} - - -/** - * MATERIAL DATA FUNCTIONS - */ - -function clearMaterialData() { - let elMaterialData = getPropertyInputElement("materialData"); - deleteJSONMaterialEditor(); - elMaterialData.value = ""; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - updateProperty('materialData', elMaterialData.value, false); -} - -function newJSONMaterialEditor() { - getPropertyInputElement("materialData").classList.remove('multi-diff'); - deleteJSONMaterialEditor(); - createJSONMaterialEditor(); - let data = {}; - setMaterialEditorJSON(data); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - showSaveMaterialDataButton(); -} - -function saveMaterialData() { - saveJSONMaterialData(true); -} - -/** - * @param {boolean} noUpdate - don't update the UI, but do send a property update. - * @param {Set.} [entityIDsToUpdate] - Entity IDs to update materialData for. - */ -function setMaterialDataFromEditor(noUpdate, entityIDsToUpdate) { - let errorFound = false; - try { - materialEditor.get(); - } catch (e) { - errorFound = true; - } - - setJSONError('materialData', errorFound); - - if (errorFound) { - return; - } - let text = materialEditor.getText(); - if (noUpdate) { - EventBridge.emitWebEvent( - JSON.stringify({ - ids: [...entityIDsToUpdate], - type: "saveMaterialData", - properties: { - materialData: text - } - }) - ); - } else { - updateProperty('materialData', text, false); - } -} - -let materialEditor = null; - -function createJSONMaterialEditor() { - let container = document.getElementById("property-materialData-editor"); - let options = { - search: false, - mode: 'tree', - modes: ['code', 'tree'], - name: 'materialData', - onError: function(e) { - alert('JSON editor:' + e); - }, - onChange: function() { - let currentJSONString = materialEditor.getText(); - - if (currentJSONString === '{"":""}') { - return; - } - $('#property-materialData-button-save').attr('disabled', false); - } - }; - materialEditor = new JSONEditor(container, options); -} - -function showSaveMaterialDataButton() { - $('#property-materialData-button-save').show(); -} - -function hideSaveMaterialDataButton() { - $('#property-materialData-button-save').hide(); -} - -function disableSaveMaterialDataButton() { - $('#property-materialData-button-save').attr('disabled', true); -} - -function showNewJSONMaterialEditorButton() { - $('#property-materialData-button-edit').show(); -} - -function hideNewJSONMaterialEditorButton() { - $('#property-materialData-button-edit').hide(); -} - -function showMaterialDataTextArea() { - $('#property-materialData').show(); -} - -function hideMaterialDataTextArea() { - $('#property-materialData').hide(); -} - -function hideMaterialDataSaved() { - $('#property-materialData-saved').hide(); -} - -function setMaterialEditorJSON(json) { - materialEditor.set(json); - if (materialEditor.hasOwnProperty('expandAll')) { - materialEditor.expandAll(); - } -} - -function deleteJSONMaterialEditor() { - if (materialEditor !== null) { - setJSONError('materialData', false); - materialEditor.destroy(); - materialEditor = null; - } -} - -let savedMaterialJSONTimer = null; - -/** - * @param {boolean} noUpdate - don't update the UI, but do send a property update. - * @param {Set.} [entityIDsToUpdate] - Entity IDs to update materialData for. - */ -function saveJSONMaterialData(noUpdate, entityIDsToUpdate) { - setMaterialDataFromEditor(noUpdate, entityIDsToUpdate ? entityIDsToUpdate : selectedEntityIDs); - $('#property-materialData-saved').show(); - $('#property-materialData-button-save').attr('disabled', true); - if (savedMaterialJSONTimer !== null) { - clearTimeout(savedMaterialJSONTimer); - } - savedMaterialJSONTimer = setTimeout(function() { - hideMaterialDataSaved(); - }, EDITOR_TIMEOUT_DURATION); -} - -function bindAllNonJSONEditorElements() { - let inputs = $('input'); - let i; - for (i = 0; i < inputs.length; ++i) { - let input = inputs[i]; - let field = $(input); - // TODO FIXME: (JSHint) Functions declared within loops referencing - // an outer scoped variable may lead to confusing semantics. - field.on('focus', function(e) { - if (e.target.id === "property-userData-button-edit" || e.target.id === "property-userData-button-clear" || - e.target.id === "property-materialData-button-edit" || e.target.id === "property-materialData-button-clear") { - return; - } - if ($('#property-userData-editor').css('height') !== "0px") { - saveUserData(); - } - if ($('#property-materialData-editor').css('height') !== "0px") { - saveMaterialData(); - } - }); - } -} - - -/** - * DROPDOWN FUNCTIONS - */ - -function setDropdownText(dropdown) { - let lis = dropdown.parentNode.getElementsByTagName("li"); - let text = ""; - for (let i = 0; i < lis.length; ++i) { - if (String(lis[i].getAttribute("value")) === String(dropdown.value)) { - text = lis[i].textContent; - } - } - dropdown.firstChild.textContent = text; -} - -function toggleDropdown(event) { - let element = event.target; - if (element.nodeName !== "DT") { - element = element.parentNode; - } - element = element.parentNode; - let isDropped = element.getAttribute("dropped"); - element.setAttribute("dropped", isDropped !== "true" ? "true" : "false"); -} - -function closeAllDropdowns() { - let elDropdowns = document.querySelectorAll("div.dropdown > dl"); - for (let i = 0; i < elDropdowns.length; ++i) { - elDropdowns[i].setAttribute('dropped', 'false'); - } -} - -function setDropdownValue(event) { - let dt = event.target.parentNode.parentNode.previousSibling.previousSibling; - dt.value = event.target.getAttribute("value"); - dt.firstChild.textContent = event.target.textContent; - - dt.parentNode.setAttribute("dropped", "false"); - - let evt = document.createEvent("HTMLEvents"); - evt.initEvent("change", true, true); - dt.dispatchEvent(evt); -} - - -/** - * TEXTAREA FUNCTIONS - */ - -function setTextareaScrolling(element) { - let isScrolling = element.scrollHeight > element.offsetHeight; - element.setAttribute("scrolling", isScrolling ? "true" : "false"); -} - -/** - * ZONE SELECTOR FUNCTIONS - */ - -function enableAllMultipleZoneSelector() { - let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection"); - let i, propId; - for (i = 0; i < allMultiZoneSelectors.length; i++) { - propId = allMultiZoneSelectors[i].id; - displaySelectedZones(propId, true); - } -} - -function disableAllMultipleZoneSelector() { - let allMultiZoneSelectors = document.querySelectorAll(".hiddenMultiZonesSelection"); - let i, propId; - for (i = 0; i < allMultiZoneSelectors.length; i++) { - propId = allMultiZoneSelectors[i].id; - displaySelectedZones(propId, false); - } -} - -function requestZoneList() { - EventBridge.emitWebEvent(JSON.stringify({ - type: "zoneListRequest" - })); -} - -function addZoneToZonesSelection(propertyId) { - let hiddenField = document.getElementById(propertyId); - if (JSON.stringify(hiddenField.value) === '"undefined"') { - hiddenField.value = "[]"; - } - let selectedZones = JSON.parse(hiddenField.value); - let zoneToAdd = document.getElementById("zones-select-" + propertyId).value; - if (!selectedZones.includes(zoneToAdd)) { - selectedZones.push(zoneToAdd); - } - hiddenField.value = JSON.stringify(selectedZones); - displaySelectedZones(propertyId, true); - let propertyName = propertyId.replace("property-", ""); - updateProperty(propertyName, selectedZones, false); -} - -function removeZoneFromZonesSelection(propertyId, zoneId) { - let hiddenField = document.getElementById(propertyId); - if (JSON.stringify(hiddenField.value) === '"undefined"') { - hiddenField.value = "[]"; - } - let selectedZones = JSON.parse(hiddenField.value); - let index = selectedZones.indexOf(zoneId); - if (index > -1) { - selectedZones.splice(index, 1); - } - hiddenField.value = JSON.stringify(selectedZones); - displaySelectedZones(propertyId, true); - let propertyName = propertyId.replace("property-", ""); - updateProperty(propertyName, selectedZones, false); -} - -function displaySelectedZones(propertyId, isEditable) { - let i,j, name, listedZoneInner, hiddenData, isMultiple; - hiddenData = document.getElementById(propertyId).value; - if (JSON.stringify(hiddenData) === '"undefined"') { - isMultiple = true; - hiddenData = "[]"; - } else { - isMultiple = false; - } - let selectedZones = JSON.parse(hiddenData); - listedZoneInner = ""; - if (selectedZones.length === 0) { - if (!isMultiple) { - listedZoneInner += ""; - } else { - listedZoneInner += ""; - } - } else { - for (i = 0; i < selectedZones.length; i++) { - name = "{ERROR: NOT FOUND}"; - for (j = 0; j < zonesList.length; j++) { - if (selectedZones[i] === zonesList[j].id) { - if (zonesList[j].name !== "") { - name = zonesList[j].name; - } else { - name = zonesList[j].id; - } - break; - } - } - if (isEditable) { - listedZoneInner += ""; - } else { - listedZoneInner += ""; - } - } - } - listedZoneInner += "
  
[ WARNING: Any changes will apply to all. ] 
" + name + ""; - listedZoneInner += "
" + name + " 
"; - document.getElementById("selected-zones-" + propertyId).innerHTML = listedZoneInner; - if (isEditable) { - document.getElementById("multiZoneSelTools-" + propertyId).style.display = "block"; - } else { - document.getElementById("multiZoneSelTools-" + propertyId).style.display = "none"; - } -} - -function createZonesSelection(property, elProperty) { - let elementID = property.elementID; - requestZoneList(); - elProperty.className = "multipleZonesSelection"; - let elInput = document.createElement('input'); - elInput.setAttribute("id", elementID); - elInput.setAttribute("type", "hidden"); - elInput.className = "hiddenMultiZonesSelection"; - - let elZonesSelector = document.createElement('div'); - elZonesSelector.setAttribute("id", "zones-selector-" + elementID); - - let elMultiDiff = document.createElement('span'); - elMultiDiff.className = "multi-diff"; - - elProperty.appendChild(elInput); - elProperty.appendChild(elZonesSelector); - elProperty.appendChild(elMultiDiff); - - return elInput; -} - -function setZonesSelectionData(element, isEditable) { - let zoneSelectorContainer = document.getElementById("zones-selector-" + element.id); - let zoneSelector = "
"; - zoneSelector += "
"; - zoneSelectorContainer.innerHTML = zoneSelector; - displaySelectedZones(element.id, isEditable); -} - -function updateAllZoneSelect() { - let allZoneSelects = document.querySelectorAll(".zoneSelect"); - let i, j, name, propId; - for (i = 0; i < allZoneSelects.length; i++) { - allZoneSelects[i].options.length = 0; - for (j = 0; j < zonesList.length; j++) { - if (zonesList[j].name === "") { - name = zonesList[j].id; - } else { - name = zonesList[j].name; - } - allZoneSelects[i].options[j] = new Option(name, zonesList[j].id, false , false); - } - propId = allZoneSelects[i].id.replace("zones-select-", ""); - if (document.getElementById("multiZoneSelTools-" + propId).style.display === "block") { - displaySelectedZones(propId, true); - } else { - displaySelectedZones(propId, false); - } - } -} - -/** - * MATERIAL TARGET FUNCTIONS - */ - -function requestMaterialTarget() { - EventBridge.emitWebEvent(JSON.stringify({ - type: 'materialTargetRequest', - entityID: getFirstSelectedID(), - })); -} - -function setMaterialTargetData(materialTargetData) { - let elDivOptions = getPropertyInputElement("parentMaterialName"); - resetDynamicMultiselectProperty(elDivOptions); - - if (materialTargetData === undefined) { - return; - } - - elDivOptions.firstChild.style.display = "none"; // hide "No Options" text - elDivOptions.parentNode.lastChild.style.display = null; // show Select/Clear all buttons - - let numMeshes = materialTargetData.numMeshes; - for (let i = 0; i < numMeshes; ++i) { - addMaterialTarget(elDivOptions, i, false); - } - - let materialNames = materialTargetData.materialNames; - let materialNamesAdded = []; - for (let i = 0; i < materialNames.length; ++i) { - let materialName = materialNames[i]; - if (materialNamesAdded.indexOf(materialName) === -1) { - addMaterialTarget(elDivOptions, materialName, true); - materialNamesAdded.push(materialName); - } - } - - materialTargetPropertyUpdate(elDivOptions.propertyValue); -} - -function addMaterialTarget(elDivOptions, targetID, isMaterialName) { - let elementID = elDivOptions.getAttribute("id"); - elementID += isMaterialName ? "-material-" : "-mesh-"; - elementID += targetID; - - let elDiv = document.createElement('div'); - elDiv.className = "materialTargetDiv"; - elDiv.onclick = onToggleMaterialTarget; - elDivOptions.appendChild(elDiv); - - let elInput = document.createElement('input'); - elInput.className = "materialTargetInput"; - elInput.setAttribute("type", "checkbox"); - elInput.setAttribute("id", elementID); - elInput.setAttribute("targetID", targetID); - elInput.setAttribute("isMaterialName", isMaterialName); - elDiv.appendChild(elInput); - - let elLabel = document.createElement('label'); - elLabel.setAttribute("for", elementID); - elLabel.innerText = isMaterialName ? "Material " + targetID : "Mesh Index " + targetID; - elDiv.appendChild(elLabel); - - return elDiv; -} - -function onToggleMaterialTarget(event) { - let elTarget = event.target; - if (elTarget instanceof HTMLInputElement) { - sendMaterialTargetProperty(); - } - event.stopPropagation(); -} - -function setAllMaterialTargetInputs(checked) { - let elDivOptions = getPropertyInputElement("parentMaterialName"); - let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); - for (let i = 0; i < elInputs.length; ++i) { - elInputs[i].checked = checked; - } -} - -function selectAllMaterialTarget() { - setAllMaterialTargetInputs(true); - sendMaterialTargetProperty(); -} - -function clearAllMaterialTarget() { - setAllMaterialTargetInputs(false); - sendMaterialTargetProperty(); -} - -function sendMaterialTargetProperty() { - let elDivOptions = getPropertyInputElement("parentMaterialName"); - let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); - - let materialTargetList = []; - for (let i = 0; i < elInputs.length; ++i) { - let elInput = elInputs[i]; - if (elInput.checked) { - let targetID = elInput.getAttribute("targetID"); - if (elInput.getAttribute("isMaterialName") === "true") { - materialTargetList.push(MATERIAL_PREFIX_STRING + targetID); - } else { - materialTargetList.push(targetID); - } - } - } - - let propertyValue = materialTargetList.join(","); - if (propertyValue.length > 1) { - propertyValue = "[" + propertyValue + "]"; - } - - updateProperty("parentMaterialName", propertyValue, false); -} - -function materialTargetPropertyUpdate(propertyValue) { - let elDivOptions = getPropertyInputElement("parentMaterialName"); - let elInputs = elDivOptions.getElementsByClassName("materialTargetInput"); - - if (propertyValue.startsWith('[')) { - propertyValue = propertyValue.substring(1, propertyValue.length); - } - if (propertyValue.endsWith(']')) { - propertyValue = propertyValue.substring(0, propertyValue.length - 1); - } - - let materialTargets = propertyValue.split(","); - for (let i = 0; i < elInputs.length; ++i) { - let elInput = elInputs[i]; - let targetID = elInput.getAttribute("targetID"); - let materialTargetName = targetID; - if (elInput.getAttribute("isMaterialName") === "true") { - materialTargetName = MATERIAL_PREFIX_STRING + targetID; - } - elInput.checked = materialTargets.indexOf(materialTargetName) >= 0; - } - - elDivOptions.propertyValue = propertyValue; -} - -function roundAndFixNumber(number, propertyData) { - let result = number; - if (propertyData.round !== undefined) { - result = Math.round(result * propertyData.round) / propertyData.round; - } - if (propertyData.decimals !== undefined) { - return result.toFixed(propertyData.decimals) - } - return result; -} - -function applyInputNumberPropertyModifiers(number, propertyData) { - const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - return roundAndFixNumber(number / multiplier, propertyData); -} - -function applyOutputNumberPropertyModifiers(number, propertyData) { - const multiplier = propertyData.multiplier !== undefined ? propertyData.multiplier : 1; - return roundAndFixNumber(number * multiplier, propertyData); -} - -const areSetsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)); - - -function handleEntitySelectionUpdate(selections, isPropertiesToolUpdate) { - const previouslySelectedEntityIDs = selectedEntityIDs; - currentSelections = selections; - selectedEntityIDs = new Set(selections.map(selection => selection.id)); - const multipleSelections = currentSelections.length > 1; - const hasSelectedEntityChanged = !areSetsEqual(selectedEntityIDs, previouslySelectedEntityIDs); - - requestZoneList(); - - if (selections.length === 0) { - deleteJSONEditor(); - deleteJSONMaterialEditor(); - - resetProperties(); - showGroupsForType("None"); - - let elIcon = properties.type.elSpan; - elIcon.innerText = NO_SELECTION; - elIcon.style.display = 'inline-block'; - - getPropertyInputElement("userData").value = ""; - showUserDataTextArea(); - showSaveUserDataButton(); - showNewJSONEditorButton(); - - getPropertyInputElement("materialData").value = ""; - showMaterialDataTextArea(); - showSaveMaterialDataButton(); - showNewJSONMaterialEditorButton(); - - disableProperties(); - } else { - if (!isPropertiesToolUpdate && !hasSelectedEntityChanged && document.hasFocus()) { - // in case the selection has not changed and we still have focus on the properties page, - // we will ignore the event. - return; - } - - if (hasSelectedEntityChanged) { - if (!multipleSelections) { - resetServerScriptStatus(); - } - } - - const doSelectElement = !hasSelectedEntityChanged; - - // Get unique entity types, and convert the types Sphere and Box to Shape - const shapeTypes = ["Sphere", "Box"]; - const entityTypes = [...new Set(currentSelections.map(a => - shapeTypes.includes(a.properties.type) ? "Shape" : a.properties.type))]; - - const shownGroups = getGroupsForTypes(entityTypes); - showGroupsForTypes(entityTypes); - showOnTheSamePage(entityTypes); - - const lockedMultiValue = getMultiplePropertyValue('locked'); - - if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) { - disableProperties(); - getPropertyInputElement('locked').removeAttribute('disabled'); - } else { - enableProperties(); - disableSaveUserDataButton(); - disableSaveMaterialDataButton(); - } - - const certificateIDMultiValue = getMultiplePropertyValue('certificateID'); - const hasCertifiedInSelection = certificateIDMultiValue.isMultiDiffValue || certificateIDMultiValue.value !== ""; - - Object.entries(properties).forEach(function([propertyID, property]) { - const propertyData = property.data; - const propertyName = property.name; - let propertyMultiValue = getMultiplePropertyValue(propertyName); - let isMultiDiffValue = propertyMultiValue.isMultiDiffValue; - let propertyValue = propertyMultiValue.value; - - if (propertyData.selectionVisibility !== undefined) { - let visibility = propertyData.selectionVisibility; - let propertyVisible = true; - if (!multipleSelections) { - propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.SINGLE_SELECTION); - } else if (isMultiDiffValue) { - propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTI_DIFF_SELECTIONS); - } else { - propertyVisible = isFlagSet(visibility, PROPERTY_SELECTION_VISIBILITY.MULTIPLE_SELECTIONS); - } - setPropertyVisibility(property, propertyVisible); - } - - const isSubProperty = propertyData.subPropertyOf !== undefined; - if (propertyValue === undefined && !isMultiDiffValue && !isSubProperty) { - return; - } - - if (!shownGroups.includes(property.group_id)) { - const WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS = false; - if (WANT_DEBUG_SHOW_HIDDEN_FROM_GROUPS) { - console.log("Skipping property " + property.data.label + " [" + property.name + - "] from hidden group " + property.group_id); - } - return; - } - - if (propertyData.hideIfCertified && hasCertifiedInSelection) { - propertyValue = "** Certified **"; - property.elInput.disabled = true; - } - - if (propertyName === "type") { - propertyValue = entityTypes.length > 1 ? "Multiple" : propertyMultiValue.values[0]; - } - - switch (propertyData.type) { - case 'string': { - if (isMultiDiffValue) { - if (propertyData.readOnly && propertyData.multiDisplayMode - && propertyData.multiDisplayMode === PROPERTY_MULTI_DISPLAY_MODE.COMMA_SEPARATED_VALUES) { - property.elInput.value = propertyMultiValue.values.join(", "); - } else { - property.elInput.classList.add('multi-diff'); - property.elInput.value = ""; - } - } else { - property.elInput.classList.remove('multi-diff'); - property.elInput.value = propertyValue; - } - break; - } - case 'bool': { - const inverse = propertyData.inverse !== undefined ? propertyData.inverse : false; - if (isSubProperty) { - let subPropertyMultiValue = getMultiplePropertyValue(propertyData.subPropertyOf); - let propertyValue = subPropertyMultiValue.value; - isMultiDiffValue = subPropertyMultiValue.isMultiDiffValue; - if (isMultiDiffValue) { - let detailedSubProperty = getDetailedSubPropertyMPVDiff(subPropertyMultiValue, propertyName); - property.elInput.checked = detailedSubProperty.isChecked; - property.elInput.classList.toggle('multi-diff', detailedSubProperty.isMultiDiff); - } else { - let subProperties = propertyValue.split(","); - let subPropertyValue = subProperties.indexOf(propertyName) > -1; - property.elInput.checked = inverse ? !subPropertyValue : subPropertyValue; - property.elInput.classList.remove('multi-diff'); - } - - } else { - if (isMultiDiffValue) { - property.elInput.checked = false; - } else { - property.elInput.checked = inverse ? !propertyValue : propertyValue; - } - property.elInput.classList.toggle('multi-diff', isMultiDiffValue); - } - - break; - } - case 'number': { - property.elInput.value = isMultiDiffValue ? "" : propertyValue; - property.elInput.classList.toggle('multi-diff', isMultiDiffValue); - break; - } - case 'number-draggable': { - let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); - property.elNumber.setValue(detailedNumberDiff.averagePerPropertyComponent[0], detailedNumberDiff.propertyComponentDiff[0]); - break; - } - case 'rect': { - let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); - property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x); - property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y); - property.elNumberWidth.setValue(detailedNumberDiff.averagePerPropertyComponent.width, detailedNumberDiff.propertyComponentDiff.width); - property.elNumberHeight.setValue(detailedNumberDiff.averagePerPropertyComponent.height, detailedNumberDiff.propertyComponentDiff.height); - break; - } - case 'vec3': - case 'vec2': { - let detailedNumberDiff = getDetailedNumberMPVDiff(propertyMultiValue, propertyData); - property.elNumberX.setValue(detailedNumberDiff.averagePerPropertyComponent.x, detailedNumberDiff.propertyComponentDiff.x); - property.elNumberY.setValue(detailedNumberDiff.averagePerPropertyComponent.y, detailedNumberDiff.propertyComponentDiff.y); - if (property.elNumberZ !== undefined) { - property.elNumberZ.setValue(detailedNumberDiff.averagePerPropertyComponent.z, detailedNumberDiff.propertyComponentDiff.z); - } - break; - } - case 'color': { - let displayColor = propertyMultiValue.isMultiDiffValue ? propertyMultiValue.values[0] : propertyValue; - property.elColorPicker.style.backgroundColor = "rgb(" + displayColor.red + "," + - displayColor.green + "," + - displayColor.blue + ")"; - property.elColorPicker.classList.toggle('multi-diff', propertyMultiValue.isMultiDiffValue); - - if (hasSelectedEntityChanged && $(property.elColorPicker).attr('active') === 'true') { - // Set the color picker inactive before setting the color, - // otherwise an update will be sent directly after setting it here. - $(property.elColorPicker).attr('active', 'false'); - colorPickers['#' + property.elementID].colpickSetColor({ - "r": displayColor.red, - "g": displayColor.green, - "b": displayColor.blue - }); - $(property.elColorPicker).attr('active', 'true'); - } - - property.elNumberR.setValue(displayColor.red); - property.elNumberG.setValue(displayColor.green); - property.elNumberB.setValue(displayColor.blue); - break; - } - case 'dropdown': { - property.elInput.classList.toggle('multi-diff', isMultiDiffValue); - property.elInput.value = isMultiDiffValue ? "" : propertyValue; - setDropdownText(property.elInput); - break; - } - case 'textarea': { - property.elInput.value = propertyValue; - setTextareaScrolling(property.elInput); - break; - } - case 'multipleZonesSelection': { - property.elInput.value = JSON.stringify(propertyValue); - if (lockedMultiValue.isMultiDiffValue || lockedMultiValue.value) { - setZonesSelectionData(property.elInput, false); - } else { - setZonesSelectionData(property.elInput, true); - } - break; - } - case 'icon': { - property.elSpan.innerHTML = propertyData.icons[propertyValue]; - property.elSpan.style.display = "inline-block"; - break; - } - case 'texture': { - property.elInput.value = isMultiDiffValue ? "" : propertyValue; - property.elInput.classList.toggle('multi-diff', isMultiDiffValue); - if (isMultiDiffValue) { - property.elInput.setMultipleValues(); - } else { - property.elInput.imageLoad(property.elInput.value); - } - break; - } - case 'dynamic-multiselect': { - if (!isMultiDiffValue && property.data.propertyUpdate) { - property.data.propertyUpdate(propertyValue); - } - break; - } - } - - let showPropertyRules = property.showPropertyRules; - if (showPropertyRules !== undefined) { - for (let propertyToShow in showPropertyRules) { - let showIfThisPropertyValue = showPropertyRules[propertyToShow]; - let show = String(propertyValue) === String(showIfThisPropertyValue); - showPropertyElement(propertyToShow, show); - } - } - }); - - updateVisibleSpaceModeProperties(); - - let userDataMultiValue = getMultiplePropertyValue("userData"); - let userDataTextArea = getPropertyInputElement("userData"); - let json = null; - if (!userDataMultiValue.isMultiDiffValue) { - try { - json = JSON.parse(userDataMultiValue.value); - } catch (e) { - - } - } - if (json !== null && !lockedMultiValue.isMultiDiffValue && !lockedMultiValue.value) { - if (editor === null) { - createJSONEditor(); - } - userDataTextArea.classList.remove('multi-diff'); - setEditorJSON(json); - showSaveUserDataButton(); - hideUserDataTextArea(); - hideNewJSONEditorButton(); - hideUserDataSaved(); - } else { - // normal text - deleteJSONEditor(); - userDataTextArea.classList.toggle('multi-diff', userDataMultiValue.isMultiDiffValue); - userDataTextArea.value = userDataMultiValue.isMultiDiffValue ? "" : userDataMultiValue.value; - - showUserDataTextArea(); - showNewJSONEditorButton(); - hideSaveUserDataButton(); - hideUserDataSaved(); - } - - let materialDataMultiValue = getMultiplePropertyValue("materialData"); - let materialDataTextArea = getPropertyInputElement("materialData"); - let materialJson = null; - if (!materialDataMultiValue.isMultiDiffValue) { - try { - materialJson = JSON.parse(materialDataMultiValue.value); - } catch (e) { - - } - } - if (materialJson !== null && !lockedMultiValue.isMultiDiffValue && !lockedMultiValue.value) { - if (materialEditor === null) { - createJSONMaterialEditor(); - } - materialDataTextArea.classList.remove('multi-diff'); - setMaterialEditorJSON(materialJson); - showSaveMaterialDataButton(); - hideMaterialDataTextArea(); - hideNewJSONMaterialEditorButton(); - hideMaterialDataSaved(); - } else { - // normal text - deleteJSONMaterialEditor(); - materialDataTextArea.classList.toggle('multi-diff', materialDataMultiValue.isMultiDiffValue); - materialDataTextArea.value = materialDataMultiValue.isMultiDiffValue ? "" : materialDataMultiValue.value; - showMaterialDataTextArea(); - showNewJSONMaterialEditorButton(); - hideSaveMaterialDataButton(); - hideMaterialDataSaved(); - } - - if (hasSelectedEntityChanged && selections.length === 1 && entityTypes[0] === "Material") { - requestMaterialTarget(); - } - - let activeElement = document.activeElement; - if (doSelectElement && typeof activeElement.select !== "undefined") { - activeElement.select(); - } - } -} - -function loaded() { - openEventBridge(function() { - let elPropertiesList = document.getElementById("properties-pages"); - let elTabs = document.getElementById("tabs"); - - GROUPS.forEach(function(group) { - let elGroup; - - elGroup = document.createElement('div'); - elGroup.className = 'section ' + "major"; - elGroup.setAttribute("id", "properties-" + group.id); - elPropertiesList.appendChild(elGroup); - - if (group.label !== undefined) { - let elLegend = document.createElement('div'); - elLegend.className = "tab-section-header"; - elLegend.appendChild(createElementFromHTML(`
${group.label}
`)); - elGroup.appendChild(elLegend); - elTabs.appendChild(createElementFromHTML('')); - } - - group.properties.forEach(function(propertyData) { - let propertyType = propertyData.type; - let propertyID = propertyData.propertyID; - let propertyName = propertyData.propertyName !== undefined ? propertyData.propertyName : propertyID; - let propertySpaceMode = propertyData.spaceMode !== undefined ? propertyData.spaceMode : PROPERTY_SPACE_MODE.ALL; - let propertyElementID = "property-" + propertyID; - propertyElementID = propertyElementID.replace('.', '-'); - - let elContainer, elLabel; - - if (propertyData.replaceID === undefined) { - // Create subheader, or create new property and append it. - if (propertyType === "sub-header") { - elContainer = createElementFromHTML( - `
${propertyData.label}
`); - } else { - elContainer = document.createElement('div'); - elContainer.setAttribute("id", "div-" + propertyElementID); - elContainer.className = 'property container'; - } - - if (group.twoColumn && propertyData.column !== undefined && propertyData.column !== -1) { - let columnName = group.id + "column" + propertyData.column; - let elColumn = document.getElementById(columnName); - if (!elColumn) { - let columnDivName = group.id + "columnDiv"; - let elColumnDiv = document.getElementById(columnDivName); - if (!elColumnDiv) { - elColumnDiv = document.createElement('div'); - elColumnDiv.className = "two-column"; - elColumnDiv.setAttribute("id", group.id + "columnDiv"); - elGroup.appendChild(elColumnDiv); - } - elColumn = document.createElement('fieldset'); - elColumn.className = "column"; - elColumn.setAttribute("id", columnName); - elColumnDiv.appendChild(elColumn); - } - elColumn.appendChild(elContainer); - } else { - elGroup.appendChild(elContainer); - } - - let labelText = propertyData.label !== undefined ? propertyData.label : ""; - let className = ''; - if (propertyData.indentedLabel || propertyData.showPropertyRule !== undefined) { - className = 'indented'; - } - elLabel = createElementFromHTML( - ``); - elContainer.appendChild(elLabel); - } else { - elContainer = document.getElementById(propertyData.replaceID); - } - - if (elLabel) { - createAppTooltip.registerTooltipElement(elLabel.childNodes[0], propertyID, propertyName); - } - - let elProperty = createElementFromHTML('
'); - elContainer.appendChild(elProperty); - - if (propertyType === 'triple') { - elProperty.className = 'flex-row'; - for (let i = 0; i < propertyData.properties.length; ++i) { - let innerPropertyData = propertyData.properties[i]; - - let elWrapper = createElementFromHTML('
'); - elProperty.appendChild(elWrapper); - - let propertyID = innerPropertyData.propertyID; - let propertyName = innerPropertyData.propertyName !== undefined ? innerPropertyData.propertyName : propertyID; - let propertyElementID = "property-" + propertyID; - propertyElementID = propertyElementID.replace('.', '-'); - - let property = createProperty(innerPropertyData, propertyElementID, propertyName, propertyID, elWrapper); - property.isParticleProperty = group.id.includes("particles"); - property.elContainer = elContainer; - property.spaceMode = propertySpaceMode; - property.group_id = group.id; - - let elLabel = createElementFromHTML(`
${innerPropertyData.label}
`); - createAppTooltip.registerTooltipElement(elLabel, propertyID, propertyName); - - elWrapper.appendChild(elLabel); - - if (property.type !== 'placeholder') { - properties[propertyID] = property; - } - if (innerPropertyData.type === 'number' || innerPropertyData.type === 'number-draggable') { - propertyRangeRequests.push(propertyID); - } - } - } else { - let property = createProperty(propertyData, propertyElementID, propertyName, propertyID, elProperty); - property.isParticleProperty = group.id.includes("particles"); - property.elContainer = elContainer; - property.spaceMode = propertySpaceMode; - property.group_id = group.id; - - if (property.type !== 'placeholder') { - properties[propertyID] = property; - } - if (propertyData.type === 'number' || propertyData.type === 'number-draggable' || - propertyData.type === 'vec2' || propertyData.type === 'vec3' || propertyData.type === 'rect') { - propertyRangeRequests.push(propertyID); - } - - let showPropertyRule = propertyData.showPropertyRule; - if (showPropertyRule !== undefined) { - let dependentProperty = Object.keys(showPropertyRule)[0]; - let dependentPropertyValue = showPropertyRule[dependentProperty]; - if (properties[dependentProperty] === undefined) { - properties[dependentProperty] = {}; - } - if (properties[dependentProperty].showPropertyRules === undefined) { - properties[dependentProperty].showPropertyRules = {}; - } - properties[dependentProperty].showPropertyRules[propertyID] = dependentPropertyValue; - } - } - }); - - elGroups[group.id] = elGroup; - }); - - updateVisibleSpaceModeProperties(); - - if (window.EventBridge !== undefined) { - EventBridge.scriptEventReceived.connect(function(data) { - data = JSON.parse(data); - if (data.type === "server_script_status" && selectedEntityIDs.size === 1) { - let elServerScriptError = document.getElementById("property-serverScripts-error"); - let elServerScriptStatus = document.getElementById("property-serverScripts-status"); - elServerScriptError.value = data.errorInfo; - // If we just set elServerScriptError's display to block or none, we still end up with - // it's parent contributing 21px bottom padding even when elServerScriptError is display:none. - // So set it's parent to block or none - elServerScriptError.parentElement.style.display = data.errorInfo ? "block" : "none"; - if (data.statusRetrieved === false) { - elServerScriptStatus.innerText = "Failed to retrieve status"; - } else if (data.isRunning) { - elServerScriptStatus.innerText = ENTITY_SCRIPT_STATUS[data.status] || data.status; - } else { - elServerScriptStatus.innerText = NOT_RUNNING_SCRIPT_STATUS; - } - } else if (data.type === "update" && data.selections) { - if (data.spaceMode !== undefined) { - currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD; - } - handleEntitySelectionUpdate(data.selections, data.isPropertiesToolUpdate); - } else if (data.type === 'tooltipsReply') { - createAppTooltip.setIsEnabled(!data.hmdActive); - createAppTooltip.setTooltipData(data.tooltips); - } else if (data.type === 'hmdActiveChanged') { - createAppTooltip.setIsEnabled(!data.hmdActive); - } else if (data.type === 'setSpaceMode') { - currentSpaceMode = data.spaceMode === "local" ? PROPERTY_SPACE_MODE.LOCAL : PROPERTY_SPACE_MODE.WORLD; - updateVisibleSpaceModeProperties(); - } else if (data.type === 'propertyRangeReply') { - let propertyRanges = data.propertyRanges; - for (let property in propertyRanges) { - let propertyRange = propertyRanges[property]; - if (propertyRange !== undefined) { - let propertyData = properties[property].data; - let multiplier = propertyData.multiplier; - if (propertyData.min === undefined && propertyRange.minimum !== "") { - propertyData.min = propertyRange.minimum; - if (multiplier !== undefined) { - propertyData.min /= multiplier; - } - } - if (propertyData.max === undefined && propertyRange.maximum !== "") { - propertyData.max = propertyRange.maximum; - if (multiplier !== undefined) { - propertyData.max /= multiplier; - } - } - switch (propertyData.type) { - case 'number': - updateNumberMinMax(properties[property]); - break; - case 'number-draggable': - updateNumberDraggableMinMax(properties[property]); - break; - case 'vec3': - case 'vec2': - updateVectorMinMax(properties[property]); - break; - case 'rect': - updateRectMinMax(properties[property]); - break; - } - } - } - } else if (data.type === 'materialTargetReply') { - if (data.entityID === getFirstSelectedID()) { - setMaterialTargetData(data.materialTargetData); - } - } else if (data.type === 'zoneListRequest') { - zonesList = data.zones; - updateAllZoneSelect(); - } - }); - - // Request tooltips and property ranges as soon as we can process a reply: - EventBridge.emitWebEvent(JSON.stringify({ type: 'tooltipsRequest' })); - EventBridge.emitWebEvent(JSON.stringify({ type: 'propertyRangeRequest', properties: propertyRangeRequests })); - } - - // Server Script Status - let elServerScriptStatusOuter = document.getElementById('div-property-serverScriptStatus'); - let elServerScriptStatusContainer = document.getElementById('div-property-serverScriptStatus').childNodes[1]; - let serverScriptStatusElementID = 'property-serverScripts-status'; - createAppTooltip.registerTooltipElement(elServerScriptStatusOuter.childNodes[0], "serverScriptsStatus"); - let elServerScriptStatus = document.createElement('div'); - elServerScriptStatus.setAttribute("id", serverScriptStatusElementID); - elServerScriptStatusContainer.appendChild(elServerScriptStatus); - - // Server Script Error - let elServerScripts = getPropertyInputElement("serverScripts"); - let elDiv = document.createElement('div'); - elDiv.className = "property"; - let elServerScriptError = document.createElement('textarea'); - let serverScriptErrorElementID = 'property-serverScripts-error'; - elServerScriptError.setAttribute("id", serverScriptErrorElementID); - elDiv.appendChild(elServerScriptError); - elServerScriptStatusContainer.appendChild(elDiv); - - let elScript = getPropertyInputElement("script"); - elScript.parentNode.className = "url refresh"; - elServerScripts.parentNode.className = "url refresh"; - - // User Data - let userDataProperty = properties["userData"]; - let elUserData = userDataProperty.elInput; - let userDataElementID = userDataProperty.elementID; - elDiv = elUserData.parentNode; - let elUserDataEditor = document.createElement('div'); - elUserDataEditor.setAttribute("id", userDataElementID + "-editor"); - let elUserDataEditorStatus = document.createElement('div'); - elUserDataEditorStatus.setAttribute("id", userDataElementID + "-editorStatus"); - let elUserDataSaved = document.createElement('span'); - elUserDataSaved.setAttribute("id", userDataElementID + "-saved"); - elUserDataSaved.innerText = "Saved!"; - elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elUserDataSaved); - elDiv.insertBefore(elUserDataEditor, elUserData); - elDiv.insertBefore(elUserDataEditorStatus, elUserData); - - // Material Data - let materialDataProperty = properties["materialData"]; - let elMaterialData = materialDataProperty.elInput; - let materialDataElementID = materialDataProperty.elementID; - elDiv = elMaterialData.parentNode; - let elMaterialDataEditor = document.createElement('div'); - elMaterialDataEditor.setAttribute("id", materialDataElementID + "-editor"); - let elMaterialDataEditorStatus = document.createElement('div'); - elMaterialDataEditorStatus.setAttribute("id", materialDataElementID + "-editorStatus"); - let elMaterialDataSaved = document.createElement('span'); - elMaterialDataSaved.setAttribute("id", materialDataElementID + "-saved"); - elMaterialDataSaved.innerText = "Saved!"; - elDiv.childNodes[JSON_EDITOR_ROW_DIV_INDEX].appendChild(elMaterialDataSaved); - elDiv.insertBefore(elMaterialDataEditor, elMaterialData); - elDiv.insertBefore(elMaterialDataEditorStatus, elMaterialData); - - // Textarea scrollbars - let elTextareas = document.getElementsByTagName("TEXTAREA"); - - let textareaOnChangeEvent = function(event) { - setTextareaScrolling(event.target); - }; - - for (let textAreaIndex = 0, numTextAreas = elTextareas.length; textAreaIndex < numTextAreas; ++textAreaIndex) { - let curTextAreaElement = elTextareas[textAreaIndex]; - setTextareaScrolling(curTextAreaElement); - curTextAreaElement.addEventListener("input", textareaOnChangeEvent, false); - curTextAreaElement.addEventListener("change", textareaOnChangeEvent, false); - /* FIXME: Detect and update textarea scrolling attribute on resize. Unfortunately textarea doesn't have a resize - event; mouseup is a partial stand-in but doesn't handle resizing if mouse moves outside textarea rectangle. */ - curTextAreaElement.addEventListener("mouseup", textareaOnChangeEvent, false); - } - - // Dropdowns - // For each dropdown the following replacement is created in place of the original dropdown... - // Structure created: - //
- //
display textcarat
- //
- //