@@ -184,11 +184,23 @@ typedef NS_ENUM(NSUInteger, MTRDeviceWorkItemDuplicateTypeID) {
184
184
MTRDeviceWorkItemDuplicateReadTypeID = 1 ,
185
185
};
186
186
187
- @implementation MTRDeviceClusterData
187
+ @implementation MTRDeviceClusterData {
188
+ NSMutableDictionary <NSNumber *, MTRDeviceDataValueDictionary> * _attributes;
189
+ }
188
190
189
191
static NSString * const sDataVersionKey = @" dataVersion" ;
190
192
static NSString * const sAttributesKey = @" attributes" ;
191
193
194
+ - (void )storeValue : (MTRDeviceDataValueDictionary _Nullable)value forAttribute : (NSNumber *)attribute
195
+ {
196
+ _attributes[attribute] = value;
197
+ }
198
+
199
+ - (NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> *)attributes
200
+ {
201
+ return _attributes;
202
+ }
203
+
192
204
+ (BOOL )supportsSecureCoding
193
205
{
194
206
return YES ;
@@ -199,6 +211,11 @@ - (NSString *)description
199
211
return [NSString stringWithFormat: @" <MTRDeviceClusterData: dataVersion %@ attributes count %lu >" , _dataVersion, static_cast <unsigned long >(_attributes.count)];
200
212
}
201
213
214
+ - (nullable instancetype )init
215
+ {
216
+ return [self initWithDataVersion: nil attributes: nil ];
217
+ }
218
+
202
219
// Attributes dictionary is: attributeID => data-value dictionary
203
220
- (nullable instancetype )initWithDataVersion : (NSNumber * _Nullable)dataVersion attributes : (NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> * _Nullable)attributes
204
221
{
@@ -208,7 +225,8 @@ - (nullable instancetype)initWithDataVersion:(NSNumber * _Nullable)dataVersion a
208
225
}
209
226
210
227
_dataVersion = [dataVersion copy ];
211
- _attributes = [attributes copy ];
228
+ _attributes = [NSMutableDictionary dictionaryWithCapacity: attributes.count];
229
+ [_attributes addEntriesFromDictionary: attributes];
212
230
213
231
return self;
214
232
}
@@ -299,10 +317,6 @@ @interface MTRDevice ()
299
317
*/
300
318
@property (nonatomic ) BOOL reattemptingSubscription;
301
319
302
- // Read cache is attributePath => NSDictionary of value.
303
- // See MTRDeviceResponseHandler definition for value dictionary details.
304
- @property (nonatomic ) NSMutableDictionary <MTRAttributePath *, NSDictionary *> * readCache;
305
-
306
320
// Expected value cache is attributePath => NSArray of [NSDate of expiration time, NSDictionary of value, expected value ID]
307
321
// - See MTRDeviceExpectedValueFieldIndex for the definitions of indices into this array.
308
322
// See MTRDeviceResponseHandler definition for value dictionary details.
@@ -369,7 +383,6 @@ - (instancetype)initWithNodeID:(NSNumber *)nodeID controller:(MTRDeviceControlle
369
383
_deviceController = controller;
370
384
_queue
371
385
= dispatch_queue_create (" org.csa-iot.matter.framework.device.workqueue" , DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
372
- _readCache = [NSMutableDictionary dictionary ];
373
386
_expectedValueCache = [NSMutableDictionary dictionary ];
374
387
_asyncWorkQueue = [[MTRAsyncWorkQueue alloc ] initWithContext: self ];
375
388
_state = MTRDeviceStateUnknown;
@@ -1021,30 +1034,12 @@ - (void)_handleReportBegin
1021
1034
}
1022
1035
}
1023
1036
1024
- - (NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> *)_attributesForCluster : (MTRClusterPath *)clusterPath
1025
- {
1026
- os_unfair_lock_assert_owner (&self->_lock );
1027
- NSMutableDictionary * attributesToReturn = [NSMutableDictionary dictionary ];
1028
- for (MTRAttributePath * attributePath in _readCache) {
1029
- if ([attributePath.endpoint isEqualToNumber: clusterPath.endpoint] && [attributePath.cluster isEqualToNumber: clusterPath.cluster]) {
1030
- attributesToReturn[attributePath.attribute] = _readCache[attributePath];
1031
- }
1032
- }
1033
- return attributesToReturn;
1034
- }
1035
-
1036
- - (NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> *)_clusterDataForPaths : (NSSet <MTRClusterPath *> *)clusterPaths
1037
+ - (NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> *)_copiedClusterDataForPaths : (NSSet <MTRClusterPath *> *)clusterPaths
1037
1038
{
1038
1039
os_unfair_lock_assert_owner (&self->_lock );
1039
1040
NSMutableDictionary * clusterDataToReturn = [NSMutableDictionary dictionary ];
1040
1041
for (MTRClusterPath * clusterPath in clusterPaths) {
1041
- NSNumber * dataVersion = _clusterData[clusterPath].dataVersion ;
1042
- NSDictionary <NSNumber *, MTRDeviceDataValueDictionary> * attributes = nil ;
1043
- attributes = [self _attributesForCluster: clusterPath];
1044
- if (dataVersion || attributes) {
1045
- MTRDeviceClusterData * clusterData = [[MTRDeviceClusterData alloc ] initWithDataVersion: dataVersion attributes: attributes];
1046
- clusterDataToReturn[clusterPath] = clusterData;
1047
- }
1042
+ clusterDataToReturn[clusterPath] = [_clusterData[clusterPath] copy ];
1048
1043
}
1049
1044
1050
1045
return clusterDataToReturn;
@@ -1060,7 +1055,10 @@ - (void)_handleReportEnd
1060
1055
BOOL dataStoreExists = _deviceController.controllerDataStore != nil ;
1061
1056
if (dataStoreExists && _clustersToPersist.count ) {
1062
1057
MTR_LOG_DEFAULT (" %@ Storing cluster information (data version) count: %lu" , self, static_cast <unsigned long >(_clustersToPersist.count ));
1063
- NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> * clusterData = [self _clusterDataForPaths: _clustersToPersist];
1058
+ // We're going to hand out these MTRDeviceClusterData objects to our
1059
+ // storage implementation, which will try to read them later. Make sure
1060
+ // we snapshot the state here instead of handing out live copies.
1061
+ NSDictionary <MTRClusterPath *, MTRDeviceClusterData *> * clusterData = [self _copiedClusterDataForPaths: _clustersToPersist];
1064
1062
[_deviceController.controllerDataStore storeClusterData: clusterData forNodeID: _nodeID];
1065
1063
_clustersToPersist = nil ;
1066
1064
}
@@ -1221,6 +1219,42 @@ - (void)_handleEventReport:(NSArray<NSDictionary<NSString *, id> *> *)eventRepor
1221
1219
return dataVersions;
1222
1220
}
1223
1221
1222
+ - (MTRDeviceDataValueDictionary _Nullable)_cachedAttributeValueForPath : (MTRAttributePath *)path
1223
+ {
1224
+ os_unfair_lock_assert_owner (&self->_lock );
1225
+
1226
+ // We need an MTRClusterPath to do the lookup in _clusterData.
1227
+ auto * clusterPath = [MTRClusterPath clusterPathWithEndpointID: path.endpoint clusterID: path.cluster];
1228
+
1229
+ MTRDeviceClusterData * clusterData = _clusterData[clusterPath];
1230
+ if (clusterData == nil ) {
1231
+ return nil ;
1232
+ }
1233
+
1234
+ return clusterData.attributes [path.attribute];
1235
+ }
1236
+
1237
+ - (void )_setCachedAttributeValue : (MTRDeviceDataValueDictionary _Nullable)value forPath : (MTRAttributePath *)path
1238
+ {
1239
+ os_unfair_lock_assert_owner (&self->_lock );
1240
+
1241
+ // We need an MTRClusterPath to do the lookup in _clusterData.
1242
+ auto * clusterPath = [MTRClusterPath clusterPathWithEndpointID: path.endpoint clusterID: path.cluster];
1243
+
1244
+ MTRDeviceClusterData * clusterData = _clusterData[clusterPath];
1245
+ if (clusterData == nil ) {
1246
+ if (value == nil ) {
1247
+ // Nothing to do.
1248
+ return ;
1249
+ }
1250
+
1251
+ clusterData = [[MTRDeviceClusterData alloc ] init ];
1252
+ _clusterData[clusterPath] = clusterData;
1253
+ }
1254
+
1255
+ [clusterData storeValue: value forAttribute: path.attribute];
1256
+ }
1257
+
1224
1258
- (void )_createDataVersionFilterListFromDictionary : (NSDictionary <MTRClusterPath *, NSNumber *> *)dataVersions dataVersionFilterList : (DataVersionFilter **)dataVersionFilterList count : (size_t *)count sizeReduction : (size_t )sizeReduction
1225
1259
{
1226
1260
size_t maxDataVersionFilterSize = dataVersions.count ;
@@ -2192,7 +2226,7 @@ - (void)_checkExpiredExpectedValues
2192
2226
// compare with known value and mark for report if different
2193
2227
MTRAttributePath * attributePath = attributeInfo[0 ];
2194
2228
NSDictionary * attributeDataValue = attributeInfo[1 ];
2195
- NSDictionary * cachedAttributeDataValue = _readCache[ attributePath];
2229
+ NSDictionary * cachedAttributeDataValue = [ self _cachedAttributeValueForPath: attributePath];
2196
2230
if (cachedAttributeDataValue
2197
2231
&& ![self _attributeDataValue: attributeDataValue isEqualToDataValue: cachedAttributeDataValue]) {
2198
2232
[attributesToReport addObject: @{ MTRAttributePathKey : attributePath, MTRDataKey : cachedAttributeDataValue, MTRPreviousDataKey : attributeDataValue }];
@@ -2249,7 +2283,7 @@ - (void)_performScheduledExpirationCheck
2249
2283
}
2250
2284
2251
2285
// Then check read cache
2252
- NSDictionary <NSString *, id > * cachedAttributeValue = _readCache[ attributePath];
2286
+ NSDictionary <NSString *, id > * cachedAttributeValue = [ self _cachedAttributeValueForPath: attributePath];
2253
2287
if (cachedAttributeValue) {
2254
2288
return cachedAttributeValue;
2255
2289
} else {
@@ -2301,16 +2335,15 @@ - (void)_noteDataVersion:(NSNumber *)dataVersion forClusterPath:(MTRClusterPath
2301
2335
// Update data version used for subscription filtering
2302
2336
MTRDeviceClusterData * clusterData = _clusterData[clusterPath];
2303
2337
if (!clusterData) {
2304
- clusterData = [[MTRDeviceClusterData alloc ] init ];
2338
+ clusterData = [[MTRDeviceClusterData alloc ] initWithDataVersion: dataVersion attributes: nil ];
2305
2339
_clusterData[clusterPath] = clusterData;
2306
2340
dataVersionChanged = YES ;
2307
2341
} else if (![clusterData.dataVersion isEqualToNumber: dataVersion]) {
2342
+ clusterData.dataVersion = dataVersion;
2308
2343
dataVersionChanged = YES ;
2309
2344
}
2310
2345
2311
2346
if (dataVersionChanged) {
2312
- clusterData.dataVersion = dataVersion;
2313
-
2314
2347
// Mark cluster path as needing persistence if needed
2315
2348
BOOL dataStoreExists = _deviceController.controllerDataStore != nil ;
2316
2349
if (dataStoreExists) {
@@ -2361,11 +2394,12 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2361
2394
// if this is an error, report and purge cache
2362
2395
if (attributeError) {
2363
2396
shouldReportAttribute = YES ;
2397
+ previousValue = [self _cachedAttributeValueForPath: attributePath];
2364
2398
MTR_LOG_INFO (" %@ report %@ error %@ purge expected value %@ read cache %@" , self, attributePath, attributeError,
2365
- _expectedValueCache[attributePath], _readCache[attributePath] );
2399
+ _expectedValueCache[attributePath], previousValue );
2366
2400
_expectedValueCache[attributePath] = nil ;
2367
- previousValue = _readCache[attributePath];
2368
- _readCache[attributePath] = nil ;
2401
+ // TODO: Is this clearing business really what we want?
2402
+ [ self _setCachedAttributeValue: nil forPath: attributePath] ;
2369
2403
} else {
2370
2404
// First separate data version and restore data value to a form without data version
2371
2405
NSNumber * dataVersion = attributeDataValue[MTRDataVersionKey];
@@ -2377,7 +2411,8 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2377
2411
attributeDataValue = [self _dataValueWithoutDataVersion: attributeDataValue];
2378
2412
}
2379
2413
2380
- BOOL readCacheValueChanged = ![self _attributeDataValue: attributeDataValue isEqualToDataValue: _readCache[attributePath]];
2414
+ previousValue = [self _cachedAttributeValueForPath: attributePath];
2415
+ BOOL readCacheValueChanged = ![self _attributeDataValue: attributeDataValue isEqualToDataValue: previousValue];
2381
2416
// Check if attribute needs to be persisted - compare only to read cache and disregard expected values
2382
2417
if (dataStoreExists && readCacheValueChanged) {
2383
2418
[self _noteChangeForClusterPath: clusterPath];
@@ -2399,12 +2434,11 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2399
2434
// Report the attribute if a read would get a changed value. This happens
2400
2435
// when our cached value changes and no expected value exists.
2401
2436
if (readCacheValueChanged && !expectedValue) {
2402
- previousValue = _readCache[attributePath];
2403
2437
shouldReportAttribute = YES ;
2404
2438
}
2405
2439
2406
- // Now that we have grabbed previousValue, update the readCache with the attribute value.
2407
- _readCache[attributePath] = attributeDataValue;
2440
+ // Now that we have grabbed previousValue, update our cache with the attribute value.
2441
+ [ self _setCachedAttributeValue: attributeDataValue forPath: attributePath] ;
2408
2442
2409
2443
if (!shouldReportAttribute) {
2410
2444
// If an expected value exists, the attribute will not be reported at this time.
@@ -2455,42 +2489,15 @@ - (NSArray *)_getAttributesToReportWithReportedValues:(NSArray<NSDictionary<NSSt
2455
2489
return attributesToReport;
2456
2490
}
2457
2491
2458
- - (void )_setAttributeValues : (NSArray <NSDictionary *> *)attributeValues reportChanges : (BOOL )reportChanges
2459
- {
2460
- os_unfair_lock_assert_owner (&self->_lock );
2461
-
2462
- if (!attributeValues.count ) {
2463
- return ;
2464
- }
2465
-
2466
- if (reportChanges) {
2467
- [self _reportAttributes: [self _getAttributesToReportWithReportedValues: attributeValues]];
2468
- } else {
2469
- for (NSDictionary * responseValue in attributeValues) {
2470
- MTRAttributePath * path = responseValue[MTRAttributePathKey];
2471
- NSDictionary * dataValue = responseValue[MTRDataKey];
2472
- _readCache[path] = dataValue;
2473
- }
2474
- }
2475
-
2476
- // If cache is set from storage and is primed with initial configuration data, then assume the client had beeen informed in the past, and mark that the callback has been called
2477
- if ([self _isCachePrimedWithInitialConfigurationData ]) {
2478
- _delegateDeviceCachePrimedCalled = YES ;
2479
- }
2480
- }
2481
-
2482
- - (void )setAttributeValues : (NSArray <NSDictionary *> *)attributeValues reportChanges : (BOOL )reportChanges
2483
- {
2484
- MTR_LOG_INFO (" %@ setAttributeValues count: %lu reportChanges: %d" , self, static_cast <unsigned long >(attributeValues.count ), reportChanges);
2485
- std::lock_guard lock (_lock);
2486
- [self _setAttributeValues: attributeValues reportChanges: reportChanges];
2487
- }
2488
-
2489
2492
#ifdef DEBUG
2490
2493
- (NSUInteger )unitTestAttributeCount
2491
2494
{
2492
2495
std::lock_guard lock (_lock);
2493
- return _readCache.count ;
2496
+ NSUInteger count = 0 ;
2497
+ for (MTRClusterPath * path in _clusterData) {
2498
+ count += _clusterData[path].attributes .count ;
2499
+ }
2500
+ return count;
2494
2501
}
2495
2502
#endif
2496
2503
@@ -2503,25 +2510,12 @@ - (void)setClusterData:(NSDictionary<MTRClusterPath *, MTRDeviceClusterData *> *
2503
2510
2504
2511
std::lock_guard lock (_lock);
2505
2512
2506
- // For each cluster, extract and create the attribute response-value for the read cache
2507
- // TODO: consider some optimization in how the read cache is structured so there's fewer conversions from this format to what's in the cache
2508
- for (MTRClusterPath * clusterPath in clusterData) {
2509
- MTRDeviceClusterData * data = clusterData[clusterPath];
2510
- // Build and set attributes one cluster at a time to avoid creating a ton of temporary objects at a time
2511
- @autoreleasepool {
2512
- NSMutableArray * attributeValues = [NSMutableArray array ];
2513
- for (NSNumber * attributeID in data.attributes ) {
2514
- MTRAttributePath * attributePath = [MTRAttributePath attributePathWithEndpointID: clusterPath.endpoint clusterID: clusterPath.cluster attributeID: attributeID];
2515
- NSDictionary * responseValue = @{ MTRAttributePathKey : attributePath, MTRDataKey : data.attributes [attributeID] };
2516
- [attributeValues addObject: responseValue];
2517
- }
2518
- if (attributeValues.count ) {
2519
- [self _setAttributeValues: attributeValues reportChanges: NO ];
2520
- }
2521
- }
2522
- }
2523
-
2524
2513
[_clusterData addEntriesFromDictionary: clusterData];
2514
+
2515
+ // If cache is set from storage and is primed with initial configuration data, then assume the client had beeen informed in the past, and mark that the callback has been called
2516
+ if ([self _isCachePrimedWithInitialConfigurationData ]) {
2517
+ _delegateDeviceCachePrimedCalled = YES ;
2518
+ }
2525
2519
}
2526
2520
2527
2521
- (BOOL )deviceCachePrimed
@@ -2558,23 +2552,25 @@ - (void)_setExpectedValue:(NSDictionary<NSString *, id> *)expectedAttributeValue
2558
2552
// Remove previous expected value only if it's from the same setExpectedValues operation
2559
2553
NSNumber * previousExpectedValueID = previousExpectedValue[MTRDeviceExpectedValueFieldIDIndex];
2560
2554
if (previousExpectedValueID.unsignedLongLongValue == expectedValueID) {
2555
+ MTRDeviceDataValueDictionary cachedValue = [self _cachedAttributeValueForPath: attributePath];
2561
2556
if (![self _attributeDataValue: previousExpectedValue[MTRDeviceExpectedValueFieldValueIndex]
2562
- isEqualToDataValue: _readCache[attributePath] ]) {
2557
+ isEqualToDataValue: cachedValue ]) {
2563
2558
// Case of removing expected value that is different than read cache - report read cache value
2564
2559
*shouldReportValue = YES ;
2565
- *attributeValueToReport = _readCache[attributePath] ;
2560
+ *attributeValueToReport = cachedValue ;
2566
2561
*previousValue = previousExpectedValue[MTRDeviceExpectedValueFieldValueIndex];
2567
2562
_expectedValueCache[attributePath] = nil ;
2568
2563
}
2569
2564
}
2570
2565
}
2571
2566
} else {
2567
+ MTRDeviceDataValueDictionary cachedValue = [self _cachedAttributeValueForPath: attributePath];
2572
2568
if (expectedAttributeValue
2573
- && ![self _attributeDataValue: expectedAttributeValue isEqualToDataValue: _readCache[attributePath] ]) {
2569
+ && ![self _attributeDataValue: expectedAttributeValue isEqualToDataValue: cachedValue ]) {
2574
2570
// Case where new expected value is different than read cache - report new expected value
2575
2571
*shouldReportValue = YES ;
2576
2572
*attributeValueToReport = expectedAttributeValue;
2577
- *previousValue = _readCache[attributePath] ;
2573
+ *previousValue = cachedValue ;
2578
2574
} else {
2579
2575
*previousValue = nil ;
2580
2576
}
@@ -2708,7 +2704,7 @@ - (BOOL)_isCachePrimedWithInitialConfigurationData
2708
2704
os_unfair_lock_assert_owner (&self->_lock );
2709
2705
2710
2706
// Check if root node descriptor exists
2711
- NSDictionary * rootDescriptorPartsListDataValue = _readCache[ [MTRAttributePath attributePathWithEndpointID: @(kRootEndpointId ) clusterID: @(MTRClusterIDTypeDescriptorID) attributeID: @(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)]];
2707
+ MTRDeviceDataValueDictionary rootDescriptorPartsListDataValue = [ self _cachedAttributeValueForPath: [MTRAttributePath attributePathWithEndpointID: @(kRootEndpointId ) clusterID: @(MTRClusterIDTypeDescriptorID) attributeID: @(MTRAttributeIDTypeClusterDescriptorAttributePartsListID)]];
2712
2708
if (!rootDescriptorPartsListDataValue || ![MTRArrayValueType isEqualToString: rootDescriptorPartsListDataValue[MTRTypeKey]]) {
2713
2709
return NO ;
2714
2710
}
@@ -2734,7 +2730,7 @@ - (BOOL)_isCachePrimedWithInitialConfigurationData
2734
2730
MTR_LOG_ERROR (" %@ unexpected parts list item value class %@" , self, [endpoint class ]);
2735
2731
continue ;
2736
2732
}
2737
- NSDictionary * descriptorDeviceTypeListDataValue = _readCache[ [MTRAttributePath attributePathWithEndpointID: endpoint clusterID: @(MTRClusterIDTypeDescriptorID) attributeID: @(MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID)]];
2733
+ MTRDeviceDataValueDictionary descriptorDeviceTypeListDataValue = [ self _cachedAttributeValueForPath: [MTRAttributePath attributePathWithEndpointID: endpoint clusterID: @(MTRClusterIDTypeDescriptorID) attributeID: @(MTRAttributeIDTypeClusterDescriptorAttributeDeviceTypeListID)]];
2738
2734
if (![MTRArrayValueType isEqualToString: descriptorDeviceTypeListDataValue[MTRTypeKey]] || !descriptorDeviceTypeListDataValue[MTRValueKey]) {
2739
2735
return NO ;
2740
2736
}
0 commit comments