Skip to content

Commit

Permalink
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Darwin] MTRDevice cache make use of controller storage for persisten…
Browse files Browse the repository at this point in the history
…t cache

Co-authored-by: Boris Zbarsky <bzbarsky@apple.com>
jtung-apple and bzbarsky-apple committed Feb 7, 2024
1 parent 71fe503 commit b18b0a8
Showing 12 changed files with 843 additions and 66 deletions.
4 changes: 2 additions & 2 deletions src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
@@ -549,7 +549,7 @@ MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
* wildcards).
*/
MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
@interface MTRClusterPath : NSObject <NSCopying>
@interface MTRClusterPath : NSObject <NSCopying, NSSecureCoding>

@property (nonatomic, readonly, copy) NSNumber * endpoint;
@property (nonatomic, readonly, copy) NSNumber * cluster;
@@ -565,7 +565,7 @@ MTR_AVAILABLE(ios(16.4), macos(13.3), watchos(9.4), tvos(16.4))
* wildcards).
*/
MTR_AVAILABLE(ios(16.1), macos(13.0), watchos(9.1), tvos(16.1))
@interface MTRAttributePath : MTRClusterPath
@interface MTRAttributePath : MTRClusterPath <NSSecureCoding>

@property (nonatomic, readonly, copy) NSNumber * attribute;

65 changes: 65 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
@@ -2425,6 +2425,42 @@ - (id)copyWithZone:(NSZone *)zone
return [MTRClusterPath clusterPathWithEndpointID:_endpoint clusterID:_cluster];
}

static NSString * const sEndpointKey = @"endpointKey";
static NSString * const sClusterKey = @"clusterKey";

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (nullable instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if (self == nil) {
return nil;
}

_endpoint = [decoder decodeObjectOfClass:[NSNumber class] forKey:sEndpointKey];
if (_endpoint && ![_endpoint isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRClusterPath decoded %@ for endpoint, not NSNumber.", _endpoint);
return nil;
}

_cluster = [decoder decodeObjectOfClass:[NSNumber class] forKey:sClusterKey];
if (_cluster && ![_cluster isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRClusterPath decoded %@ for cluster, not NSNumber.", _cluster);
return nil;
}

return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:_endpoint forKey:sEndpointKey];
[coder encodeObject:_cluster forKey:sClusterKey];
}

@end

@implementation MTRAttributePath
@@ -2482,6 +2518,35 @@ - (ConcreteAttributePath)_asConcretePath
return ConcreteAttributePath([self.endpoint unsignedShortValue], static_cast<ClusterId>([self.cluster unsignedLongValue]),
static_cast<AttributeId>([self.attribute unsignedLongValue]));
}

static NSString * const sAttributeKey = @"attributeKey";

+ (BOOL)supportsSecureCoding
{
return YES;
}

- (nullable instancetype)initWithCoder:(NSCoder *)decoder
{
self = [super initWithCoder:decoder];
if (self == nil) {
return nil;
}

_attribute = [decoder decodeObjectOfClass:[NSNumber class] forKey:sAttributeKey];
if (_attribute && ![_attribute isKindOfClass:[NSNumber class]]) {
MTR_LOG_ERROR("MTRAttributePath decoded %@ for attribute, not NSNumber.", _attribute);
return nil;
}

return self;
}

- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:_attribute forKey:sAttributeKey];
}

@end

@implementation MTRAttributePath (Deprecated)
56 changes: 43 additions & 13 deletions src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
@@ -1503,15 +1503,20 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt

NSMutableArray * attributesToReport = [NSMutableArray array];
NSMutableArray * attributePathsToReport = [NSMutableArray array];
for (NSDictionary<NSString *, id> * attributeReponseValue in reportedAttributeValues) {
MTRAttributePath * attributePath = attributeReponseValue[MTRAttributePathKey];
NSDictionary * attributeDataValue = attributeReponseValue[MTRDataKey];
NSError * attributeError = attributeReponseValue[MTRErrorKey];
BOOL dataStoreExists = _deviceController.controllerDataStore != nil;
NSMutableArray * attributesToPersist;
if (dataStoreExists) {
attributesToPersist = [NSMutableArray array];
}
for (NSDictionary<NSString *, id> * attributeResponseValue in reportedAttributeValues) {
MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey];
NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey];
NSError * attributeError = attributeResponseValue[MTRErrorKey];
NSDictionary * previousValue;

// sanity check either data value or error must exist
if (!attributeDataValue && !attributeError) {
MTR_LOG_INFO("%@ report %@ no data value or error: %@", self, attributePath, attributeReponseValue);
MTR_LOG_INFO("%@ report %@ no data value or error: %@", self, attributePath, attributeResponseValue);
continue;
}

@@ -1532,6 +1537,12 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
previousValue = _readCache[attributePath];
_readCache[attributePath] = nil;
} else {
BOOL readCacheValueChanged = ![self _attributeDataValue:attributeDataValue isEqualToDataValue:_readCache[attributePath]];
// Check if attribute needs to be persisted - compare only to read cache and disregard expected values
if (dataStoreExists && readCacheValueChanged) {
[attributesToPersist addObject:attributeResponseValue];
}

// if expected values exists, purge and update read cache
NSArray * expectedValue = _expectedValueCache[attributePath];
if (expectedValue) {
@@ -1542,7 +1553,7 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
}
_expectedValueCache[attributePath] = nil;
_readCache[attributePath] = attributeDataValue;
} else if (![self _attributeDataValue:attributeDataValue isEqualToDataValue:_readCache[attributePath]]) {
} else if (readCacheValueChanged) {
// otherwise compare and update read cache
previousValue = _readCache[attributePath];
_readCache[attributePath] = attributeDataValue;
@@ -1580,21 +1591,40 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt

if (shouldReportAttribute) {
if (previousValue) {
NSMutableDictionary * mutableAttributeReponseValue = attributeReponseValue.mutableCopy;
mutableAttributeReponseValue[MTRPreviousDataKey] = previousValue;
[attributesToReport addObject:mutableAttributeReponseValue];
NSMutableDictionary * mutableAttributeResponseValue = attributeResponseValue.mutableCopy;
mutableAttributeResponseValue[MTRPreviousDataKey] = previousValue;
[attributesToReport addObject:mutableAttributeResponseValue];
} else {
[attributesToReport addObject:attributeReponseValue];
[attributesToReport addObject:attributeResponseValue];
}
[attributePathsToReport addObject:attributePath];
}
}

MTR_LOG_INFO("%@ report from reported values %@", self, attributePathsToReport);

if (dataStoreExists && attributesToPersist.count) {
[_deviceController.controllerDataStore storeAttributeValues:attributesToPersist forNodeID:_nodeID];
}

return attributesToReport;
}

- (void)setAttributeValues:(NSArray<NSDictionary *> *)attributeValues reportChanges:(BOOL)reportChanges
{
if (reportChanges) {
[self _handleAttributeReport:attributeValues];
} else {
os_unfair_lock_lock(&self->_lock);
for (NSDictionary * responseValue in attributeValues) {
MTRAttributePath * path = responseValue[MTRAttributePathKey];
NSDictionary * dataValue = responseValue[MTRDataKey];
_readCache[path] = dataValue;
}
os_unfair_lock_unlock(&self->_lock);
}
}

// If value is non-nil, associate with expectedValueID
// If value is nil, remove only if expectedValueID matches
// previousValue is an out parameter
@@ -1662,9 +1692,9 @@ - (NSArray *)_getAttributesToReportWithNewExpectedValues:(NSArray<NSDictionary<N

NSMutableArray * attributesToReport = [NSMutableArray array];
NSMutableArray * attributePathsToReport = [NSMutableArray array];
for (NSDictionary<NSString *, id> * attributeReponseValue in expectedAttributeValues) {
MTRAttributePath * attributePath = attributeReponseValue[MTRAttributePathKey];
NSDictionary * attributeDataValue = attributeReponseValue[MTRDataKey];
for (NSDictionary<NSString *, id> * attributeResponseValue in expectedAttributeValues) {
MTRAttributePath * attributePath = attributeResponseValue[MTRAttributePathKey];
NSDictionary * attributeDataValue = attributeResponseValue[MTRDataKey];

BOOL shouldReportValue = NO;
NSDictionary<NSString *, id> * attributeValueToReport;
6 changes: 6 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController.mm
Original file line number Diff line number Diff line change
@@ -864,6 +864,12 @@ - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID
if ([self isRunning]) {
_nodeIDToDeviceMap[nodeID] = deviceToReturn;
}

// Load persisted attributes if they exist.
NSArray * attributesFromCache = [_controllerDataStore getStoredAttributesForNodeID:nodeID];
if (attributesFromCache) {
[deviceToReturn setAttributeValues:attributesFromCache reportChanges:NO];
}
}
os_unfair_lock_unlock(&_deviceMapLock);

8 changes: 8 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceControllerDataStore.h
Original file line number Diff line number Diff line change
@@ -63,6 +63,14 @@ NS_ASSUME_NONNULL_BEGIN
- (CHIP_ERROR)storeLastLocallyUsedNOC:(MTRCertificateTLVBytes)noc;
- (MTRCertificateTLVBytes _Nullable)fetchLastLocallyUsedNOC;

/**
* Storage for MTRDevice attribute read cache. This is local-only storage as an optimization. New controller devices using MTRDevice API can prime their own local cache from devices directly.
*/
- (nullable NSArray<NSDictionary *> *)getStoredAttributesForNodeID:(NSNumber *)nodeID;
- (void)storeAttributeValues:(NSArray<NSDictionary *> *)dataValues forNodeID:(NSNumber *)nodeID;
- (void)clearStoredAttributesForNodeID:(NSNumber *)nodeID;
- (void)clearAllStoredAttributes;

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit b18b0a8

Please sign in to comment.