Skip to content

Commit

Permalink
[Darwin] MTRDevice cache make use of controller storage for persisten…
Browse files Browse the repository at this point in the history
…t cache
  • Loading branch information
jtung-apple committed Feb 6, 2024
1 parent f25b0b8 commit 11095b0
Show file tree
Hide file tree
Showing 12 changed files with 804 additions and 55 deletions.
4 changes: 2 additions & 2 deletions src/darwin/Framework/CHIP/MTRBaseDevice.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down
65 changes: 65 additions & 0 deletions src/darwin/Framework/CHIP/MTRBaseDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
32 changes: 31 additions & 1 deletion src/darwin/Framework/CHIP/MTRDevice.mm
Original file line number Diff line number Diff line change
Expand Up @@ -1503,6 +1503,11 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt

NSMutableArray * attributesToReport = [NSMutableArray array];
NSMutableArray * attributePathsToReport = [NSMutableArray array];
BOOL dataStoreExists = _deviceController.controllerDataStore != nil;
NSMutableArray * attributesToPersist;
if (dataStoreExists) {
attributesToPersist = [NSMutableArray array];
}
for (NSDictionary<NSString *, id> * attributeReponseValue in reportedAttributeValues) {
MTRAttributePath * attributePath = attributeReponseValue[MTRAttributePathKey];
NSDictionary * attributeDataValue = attributeReponseValue[MTRDataKey];
Expand Down Expand Up @@ -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:attributeReponseValue];
}

// if expected values exists, purge and update read cache
NSArray * expectedValue = _expectedValueCache[attributePath];
if (expectedValue) {
Expand All @@ -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;
Expand Down Expand Up @@ -1592,9 +1603,28 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt

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

if (dataStoreExists && attributesToPersist.count) {
[_deviceController.controllerDataStore storeInCacheAttributes: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
Expand Down
26 changes: 26 additions & 0 deletions src/darwin/Framework/CHIP/MTRDeviceController.mm
Original file line number Diff line number Diff line change
Expand Up @@ -851,6 +851,12 @@ - (MTRDevice *)deviceForNodeID:(NSNumber *)nodeID
if ([self isRunning]) {
_nodeIDToDeviceMap[nodeID] = deviceToReturn;
}

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

Expand Down Expand Up @@ -1230,6 +1236,26 @@ - (void)downloadLogFromNodeWithID:(NSNumber *)nodeID
completion:completion];
}

#pragma - Unit Test accessors for data store
#ifdef DEBUG
- (nullable NSArray<NSDictionary *> *)unitTest_dataStore_getAttributeCacheForNodeID:(NSNumber *)nodeID
{
return [_controllerDataStore getAttributeCacheForNodeID:nodeID];
}
- (void)unitTest_dataStore_storeInCacheAttributes:(NSArray<NSDictionary *> *)dataValues forNodeID:(NSNumber *)nodeID
{
[_controllerDataStore storeInCacheAttributes:dataValues forNodeID:nodeID];
}
- (void)unitTest_dataStore_clearAttributeCacheForNodeID:(NSNumber *)nodeID
{
[_controllerDataStore clearAttributeCacheForNodeID:nodeID];
}
- (void)unitTest_dataStore_clearAllAttributeCache
{
[_controllerDataStore clearAllAttributeCache];
}
#endif

@end

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

/** MTRDevice cache storage
*
* Per controller:
* NodeID index
* key: "nodeID index"
* value: list of nodeIDs
* EndpointID index
* key: "<nodeID> endpoints"
* value: list of endpoint IDs
* ClusterID index
* key: "<nodeID+endpointID> clusters"
* value: list of cluster IDs
* AttributeID index
* key: "<nodeID+endpointID+clusterID> attributes"
* value: list of attribute IDs
* Attribute data:
* key: "<nodeID+endpointID+clusterID+attributeID> attribute data"
* value: serialized dictionary of attribute data
*
* Attribute data dictionary
* Additional value "serial number"
*/
- (nullable NSArray<NSDictionary *> *)getAttributeCacheForNodeID:(NSNumber *)nodeID;
- (void)storeInCacheAttributes:(NSArray<NSDictionary *> *)dataValues forNodeID:(NSNumber *)nodeID;
- (void)clearAttributeCacheForNodeID:(NSNumber *)nodeID;
- (void)clearAllAttributeCache;

/**
* MTRDevice cache
* getAttributesToReportWithReportedValues
* Take updaes => send non-error paths and call store
* init
* Call getAttributeCacheForNodeID
*
* MTRDevieControllerDataStore
* init
* Read serial number and advance-jump / store
*
* MTRDevieControllerDataStore
* Need lock for store / get transactions to work without stomping each other
*
* TODO: Need to think about clean up for stale nodes
*/

@end

NS_ASSUME_NONNULL_END
Loading

0 comments on commit 11095b0

Please sign in to comment.