From 16bc667b95860b89f20e25494582cbf18fe9bdc0 Mon Sep 17 00:00:00 2001 From: Simon Walton Date: Fri, 22 Feb 2019 18:17:30 -0800 Subject: [PATCH] Looks like serialization order must be same a flag enum order!? Also resuscitate EntityItem debug dumps. --- assignment-client/src/avatars/AvatarMixer.cpp | 62 ++++++++++++++++++- assignment-client/src/avatars/AvatarMixer.h | 8 +++ .../src/avatars/AvatarMixerClientData.cpp | 54 +++++++++++++++- .../src/avatars/AvatarMixerClientData.h | 2 +- .../src/avatars/AvatarMixerSlave.h | 4 ++ assignment-client/src/octree/OctreeServer.cpp | 3 +- libraries/entities/src/EntityItem.cpp | 13 ++-- libraries/entities/src/EntityItem.h | 2 +- .../entities/src/EntityItemProperties.cpp | 2 +- libraries/entities/src/ZoneEntityItem.cpp | 20 +++++- libraries/entities/src/ZoneEntityItem.h | 2 + 11 files changed, 156 insertions(+), 16 deletions(-) diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 500772c1b5..f352e02afa 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -32,6 +33,8 @@ #include #include #include +#include "../AssignmentDynamicFactory.h" +#include "../entities/AssignmentParentFinder.h" const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer"; @@ -42,6 +45,9 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : ThreadedAssignment(message), _slavePool(&_slaveSharedData) { + DependencyManager::registerInheritance(); + DependencyManager::set(); + // make sure we hear about node kills so we can tell the other nodes connect(DependencyManager::get().data(), &NodeList::nodeKilled, this, &AvatarMixer::handleAvatarKilled); @@ -56,6 +62,8 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::RequestsDomainListData, this, "handleRequestsDomainListDataPacket"); packetReceiver.registerListener(PacketType::SetAvatarTraits, this, "queueIncomingPacket"); packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); + packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, + this, "handleOctreePacket"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -227,6 +235,10 @@ void AvatarMixer::start() { int lockWait, nodeTransform, functor; + { + _entityViewer.queryOctree(); + } + // Allow nodes to process any pending/queued packets across our worker threads { auto start = usecTimestampNow(); @@ -847,13 +859,15 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node void AvatarMixer::domainSettingsRequestComplete() { auto nodeList = DependencyManager::get(); nodeList->addSetOfNodeTypesToNodeInterestSet({ - NodeType::Agent, NodeType::EntityScriptServer, + NodeType::Agent, NodeType::EntityScriptServer, NodeType::EntityServer, NodeType::UpstreamAvatarMixer, NodeType::DownstreamAvatarMixer }); // parse the settings to pull out the values we need parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject()); + setupEntityQuery(); + // start our tight loop... start(); } @@ -941,3 +955,49 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) { qCDebug(avatars) << "Avatars other than" << _slaveSharedData.skeletonURLWhitelist << "will be replaced by" << (_slaveSharedData.skeletonReplacementURL.isEmpty() ? "default" : _slaveSharedData.skeletonReplacementURL.toString()); } } + +void AvatarMixer::setupEntityQuery() { + static char queryJsonString[] = R"({"avatarPriority": true})"; + + _entityViewer.init(); + DependencyManager::registerInheritance(); + DependencyManager::set(_entityViewer.getTree()); + _slaveSharedData.entityTree = _entityViewer.getTree(); + + QJsonParseError jsonParseError; + const QJsonDocument priorityZoneQuery(QJsonDocument::fromJson(queryJsonString, &jsonParseError)); + if (jsonParseError.error != QJsonParseError::NoError) { + qCDebug(avatars) << "Error parsing:" << queryJsonString << " - " << jsonParseError.errorString(); + return; + } + _entityViewer.getOctreeQuery().setJSONParameters(priorityZoneQuery.object()); + +} + +void AvatarMixer::handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode) { + PacketType packetType = message->getType(); + + switch (packetType) { + case PacketType::OctreeStats: + break; + + case PacketType::EntityData: + _entityViewer.processDatagram(*message, senderNode); + break; + + case PacketType::EntityErase: + _entityViewer.processEraseMessage(*message, senderNode); + break; + + default: + qCDebug(avatars) << "Unexpected packet type:" << packetType; + break; + } +} + +void AvatarMixer::aboutToFinish() { + DependencyManager::destroy(); + DependencyManager::destroy(); + + ThreadedAssignment::aboutToFinish(); +} diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 764656a2d5..6d82172995 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -19,6 +19,7 @@ #include #include +#include "../entities/EntityTreeHeadlessViewer.h" #include "AvatarMixerClientData.h" #include "AvatarMixerSlavePool.h" @@ -28,6 +29,7 @@ class AvatarMixer : public ThreadedAssignment { Q_OBJECT public: AvatarMixer(ReceivedMessage& message); + virtual void aboutToFinish() override; static bool shouldReplicateTo(const Node& from, const Node& to) { return to.getType() == NodeType::DownstreamAvatarMixer && @@ -56,6 +58,7 @@ private slots: void handleReplicatedBulkAvatarPacket(QSharedPointer message); void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); + void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); void start(); private: @@ -70,8 +73,13 @@ private: void optionallyReplicatePacket(ReceivedMessage& message, const Node& node); + void setupEntityQuery(); + p_high_resolution_clock::time_point _lastFrameTimestamp; + // Attach to entity tree for avatar-priority zone info. + EntityTreeHeadlessViewer _entityViewer; + // FIXME - new throttling - use these values somehow float _trailingMixRatio { 0.0f }; float _throttlingRatio { 0.0f }; diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index b7d2f5cdf8..9edbae12d8 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -16,6 +16,8 @@ #include #include +#include +#include #include "AvatarMixerSlave.h" @@ -62,7 +64,7 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData switch (packet->getType()) { case PacketType::AvatarData: - parseData(*packet); + parseData(*packet, slaveSharedData); break; case PacketType::SetAvatarTraits: processSetTraitsMessage(*packet, slaveSharedData, *node); @@ -80,7 +82,38 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData return packetsProcessed; } -int AvatarMixerClientData::parseData(ReceivedMessage& message) { +namespace { + using std::static_pointer_cast; + + // Operator to find if a point is within an avatar-priority (hero) Zone Entity. + struct FindPriorityZone { + glm::vec3 position; + bool isInPriorityZone { false }; + static bool operation(const OctreeElementPointer& element, void* extraData) { + auto findPriorityZone = static_cast(extraData); + if (element->getAACube().contains(findPriorityZone->position)) { + const EntityTreeElementPointer entityTreeElement = static_pointer_cast(element); + entityTreeElement->forEachEntity([&findPriorityZone](EntityItemPointer item) { + if (item->getType() == EntityTypes::Zone + && item->contains(findPriorityZone->position) + && static_pointer_cast(item)->getAvatarPriority()) { + findPriorityZone->isInPriorityZone = true; + } + }); + if (findPriorityZone->isInPriorityZone) { + return false; // For now, stop at first hit. + } else { + return true; + } + } else { // Position isn't within this subspace, so end recursion. + return false; + } + } + }; + +} // Close anonymous namespace. + +int AvatarMixerClientData::parseData(ReceivedMessage& message, const SlaveSharedData& slaveSharedData) { // pull the sequence number from the data first uint16_t sequenceNumber; @@ -90,9 +123,24 @@ int AvatarMixerClientData::parseData(ReceivedMessage& message) { incrementNumOutOfOrderSends(); } _lastReceivedSequenceNumber = sequenceNumber; + glm::vec3 oldPosition = getPosition(); // compute the offset to the data payload - return _avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead())); + if (!_avatar->parseDataFromBuffer(message.readWithoutCopy(message.getBytesLeftToRead()))) { + return false; + } + + auto newPosition = getPosition(); + if (newPosition != oldPosition) { + EntityTree& entityTree = *slaveSharedData.entityTree; + FindPriorityZone findPriorityZone { newPosition, false } ; + entityTree.recurseTreeWithOperation(&FindPriorityZone::operation, &findPriorityZone); + if (findPriorityZone.isInPriorityZone) { + // Tag avatar as hero ... + } + } + + return true; } void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message, diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index 843f19cf22..45d508088c 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -45,7 +45,7 @@ public: using HRCTime = p_high_resolution_clock::time_point; using PerNodeTraitVersions = std::unordered_map; - int parseData(ReceivedMessage& message) override; + int parseData(ReceivedMessage& message, const SlaveSharedData& SlaveSharedData); AvatarData& getAvatar() { return *_avatar; } const AvatarData& getAvatar() const { return *_avatar; } const AvatarData* getConstAvatarData() const { return _avatar.get(); } diff --git a/assignment-client/src/avatars/AvatarMixerSlave.h b/assignment-client/src/avatars/AvatarMixerSlave.h index 91bb02fd55..28d99748fa 100644 --- a/assignment-client/src/avatars/AvatarMixerSlave.h +++ b/assignment-client/src/avatars/AvatarMixerSlave.h @@ -90,9 +90,13 @@ public: } }; +class EntityTree; +using EntityTreePointer = std::shared_ptr; + struct SlaveSharedData { QStringList skeletonURLWhitelist; QUrl skeletonReplacementURL; + EntityTreePointer entityTree; }; class AvatarMixerSlave { diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index e993bea358..477d3dd612 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1203,7 +1203,8 @@ void OctreeServer::beginRunning() { auto nodeList = DependencyManager::get(); // we need to ask the DS about agents so we can ping/reply with them - nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer }); + nodeList->addSetOfNodeTypesToNodeInterestSet({ NodeType::Agent, NodeType::EntityScriptServer, + NodeType::AvatarMixer }); beforeRun(); // after payload has been processed diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 5c423b2fe5..25e5893078 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -41,6 +41,7 @@ #include "EntitySimulation.h" #include "EntityDynamicFactoryInterface.h" +//#define WANT_DEBUG Q_DECLARE_METATYPE(EntityItemPointer); int entityItemPointernMetaTypeId = qRegisterMetaType(); @@ -499,6 +500,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } #ifdef WANT_DEBUG + { quint64 lastEdited = getLastEdited(); float editedAgo = getEditedAgo(); QString agoAsString = formatSecondsElapsed(editedAgo); @@ -512,6 +514,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef qCDebug(entities) << " age=" << getAge() << "seconds - " << ageAsString; qCDebug(entities) << " lastEdited =" << lastEdited; qCDebug(entities) << " ago=" << editedAgo << "seconds - " << agoAsString; + } #endif quint64 lastEditedFromBuffer = 0; @@ -1096,7 +1099,7 @@ void EntityItem::simulate(const quint64& now) { qCDebug(entities) << " hasGravity=" << hasGravity(); qCDebug(entities) << " hasAcceleration=" << hasAcceleration(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); - qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); + qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity(); qCDebug(entities) << " isMortal=" << isMortal(); qCDebug(entities) << " getAge()=" << getAge(); qCDebug(entities) << " getLifetime()=" << getLifetime(); @@ -1108,12 +1111,12 @@ void EntityItem::simulate(const quint64& now) { qCDebug(entities) << " hasGravity=" << hasGravity(); qCDebug(entities) << " hasAcceleration=" << hasAcceleration(); qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); - qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); + qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity(); } if (hasAngularVelocity()) { qCDebug(entities) << " CHANGING...="; qCDebug(entities) << " hasAngularVelocity=" << hasAngularVelocity(); - qCDebug(entities) << " getAngularVelocity=" << getAngularVelocity(); + qCDebug(entities) << " getAngularVelocity=" << getLocalAngularVelocity(); } if (isMortal()) { qCDebug(entities) << " MORTAL...="; @@ -2649,13 +2652,13 @@ bool EntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { // ALL entity properties. Some work will need to be done to the property system so that it can be more flexible // (to grab the value and default value of a property given the string representation of that property, for example) - // currently the only property filter we handle is '+' for serverScripts + // currently the only property filter we handle in EntityItem is '+' for serverScripts // which means that we only handle a filtered query asking for entities where the serverScripts property is non-default static const QString SERVER_SCRIPTS_PROPERTY = "serverScripts"; foreach(const auto& property, jsonFilters.keys()) { - if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) { + if (property == SERVER_SCRIPTS_PROPERTY && jsonFilters[property] == EntityQueryFilterSymbol::NonDefault) { // check if this entity has a non-default value for serverScripts if (_serverScripts != ENTITY_ITEM_DEFAULT_SERVER_SCRIPTS) { return true; diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index ec7ad78313..87dca3f314 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -516,7 +516,7 @@ public: QUuid getLastEditedBy() const { return _lastEditedBy; } void setLastEditedBy(QUuid value) { _lastEditedBy = value; } - bool matchesJSONFilters(const QJsonObject& jsonFilters) const; + virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const; virtual bool getMeshes(MeshProxyList& result) { return true; } diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 103f5dbab7..96160ebd93 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -3178,13 +3178,13 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, properties.getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, properties.getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, properties.getFilterURL()); - APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, properties.getAvatarPriority()); APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)properties.getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)properties.getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)properties.getSkyboxMode()); APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)properties.getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)properties.getBloomMode()); + APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, properties.getAvatarPriority()); } if (properties.getType() == EntityTypes::PolyVox) { diff --git a/libraries/entities/src/ZoneEntityItem.cpp b/libraries/entities/src/ZoneEntityItem.cpp index 13c7273d94..83619caa3c 100644 --- a/libraries/entities/src/ZoneEntityItem.cpp +++ b/libraries/entities/src/ZoneEntityItem.cpp @@ -112,13 +112,13 @@ bool ZoneEntityItem::setSubClassProperties(const EntityItemProperties& propertie SET_ENTITY_PROPERTY_FROM_PROPERTIES(flyingAllowed, setFlyingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ghostingAllowed, setGhostingAllowed); SET_ENTITY_PROPERTY_FROM_PROPERTIES(filterURL, setFilterURL); - SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); SET_ENTITY_PROPERTY_FROM_PROPERTIES(keyLightMode, setKeyLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(ambientLightMode, setAmbientLightMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(skyboxMode, setSkyboxMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(hazeMode, setHazeMode); SET_ENTITY_PROPERTY_FROM_PROPERTIES(bloomMode, setBloomMode); + SET_ENTITY_PROPERTY_FROM_PROPERTIES(avatarPriority, setAvatarPriority); somethingChanged = somethingChanged || _keyLightPropertiesChanged || _ambientLightPropertiesChanged || _stagePropertiesChanged || _skyboxPropertiesChanged || _hazePropertiesChanged || _bloomPropertiesChanged; @@ -188,13 +188,13 @@ int ZoneEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data, READ_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, bool, setFlyingAllowed); READ_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, bool, setGhostingAllowed); READ_ENTITY_PROPERTY(PROP_FILTER_URL, QString, setFilterURL); - READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, bool, setAvatarPriority); READ_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, uint32_t, setKeyLightMode); READ_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, uint32_t, setAmbientLightMode); READ_ENTITY_PROPERTY(PROP_SKYBOX_MODE, uint32_t, setSkyboxMode); READ_ENTITY_PROPERTY(PROP_HAZE_MODE, uint32_t, setHazeMode); READ_ENTITY_PROPERTY(PROP_BLOOM_MODE, uint32_t, setBloomMode); + READ_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, bool, setAvatarPriority); return bytesRead; } @@ -254,13 +254,13 @@ void ZoneEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBits APPEND_ENTITY_PROPERTY(PROP_FLYING_ALLOWED, getFlyingAllowed()); APPEND_ENTITY_PROPERTY(PROP_GHOSTING_ALLOWED, getGhostingAllowed()); APPEND_ENTITY_PROPERTY(PROP_FILTER_URL, getFilterURL()); - APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority()); APPEND_ENTITY_PROPERTY(PROP_KEY_LIGHT_MODE, (uint32_t)getKeyLightMode()); APPEND_ENTITY_PROPERTY(PROP_AMBIENT_LIGHT_MODE, (uint32_t)getAmbientLightMode()); APPEND_ENTITY_PROPERTY(PROP_SKYBOX_MODE, (uint32_t)getSkyboxMode()); APPEND_ENTITY_PROPERTY(PROP_HAZE_MODE, (uint32_t)getHazeMode()); APPEND_ENTITY_PROPERTY(PROP_BLOOM_MODE, (uint32_t)getBloomMode()); + APPEND_ENTITY_PROPERTY(PROP_AVATAR_PRIORITY, getAvatarPriority()); } void ZoneEntityItem::debugDump() const { @@ -274,6 +274,7 @@ void ZoneEntityItem::debugDump() const { qCDebug(entities) << " _ambientLightMode:" << EntityItemProperties::getComponentModeAsString(_ambientLightMode); qCDebug(entities) << " _skyboxMode:" << EntityItemProperties::getComponentModeAsString(_skyboxMode); qCDebug(entities) << " _bloomMode:" << EntityItemProperties::getComponentModeAsString(_bloomMode); + qCDebug(entities) << " _avatarPriority:" << getAvatarPriority(); _keyLightProperties.debugDump(); _ambientLightProperties.debugDump(); @@ -469,3 +470,16 @@ void ZoneEntityItem::fetchCollisionGeometryResource() { _shapeResource = DependencyManager::get()->getCollisionGeometryResource(hullURL); } } + +bool ZoneEntityItem::matchesJSONFilters(const QJsonObject& jsonFilters) const { + // currently the only property filter we handle in ZoneEntityItem is value of avatarPriority + + static const QString AVATAR_PRIORITY_PROPERTY = "avatarPriority"; + + if (jsonFilters.contains(AVATAR_PRIORITY_PROPERTY)) { + return (jsonFilters[AVATAR_PRIORITY_PROPERTY].toBool() == _avatarPriority); + } + + // Chain to base: + return EntityItem::matchesJSONFilters(jsonFilters); +} diff --git a/libraries/entities/src/ZoneEntityItem.h b/libraries/entities/src/ZoneEntityItem.h index 20bab7c710..a3e668b6f6 100644 --- a/libraries/entities/src/ZoneEntityItem.h +++ b/libraries/entities/src/ZoneEntityItem.h @@ -66,6 +66,8 @@ public: QString getCompoundShapeURL() const; virtual void setCompoundShapeURL(const QString& url); + virtual bool matchesJSONFilters(const QJsonObject& jsonFilters) const override; + KeyLightPropertyGroup getKeyLightProperties() const { return resultWithReadLock([&] { return _keyLightProperties; }); } AmbientLightPropertyGroup getAmbientLightProperties() const { return resultWithReadLock([&] { return _ambientLightProperties; }); }