Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 2 // for details. All rights reserved. Use of this source code is governed by a |
| 3 // BSD-style license that can be found in the LICENSE file. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 library observe.src.path_observer; | 5 library observe.src.path_observer; |
| 6 | 6 |
| 7 import 'dart:async'; | 7 import 'dart:async'; |
| 8 import 'dart:collection'; | 8 import 'dart:collection'; |
| 9 import 'dart:math' show min; | 9 import 'dart:math' show min; |
| 10 | 10 |
| (...skipping 114 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 125 throw new ArgumentError( | 125 throw new ArgumentError( |
| 126 'List must contain only ints, Strings, and Symbols'); | 126 'List must contain only ints, Strings, and Symbols'); |
| 127 } | 127 } |
| 128 } | 128 } |
| 129 return new PropertyPath._(copy); | 129 return new PropertyPath._(copy); |
| 130 } | 130 } |
| 131 | 131 |
| 132 var pathObj = _pathCache[path]; | 132 var pathObj = _pathCache[path]; |
| 133 if (pathObj != null) return pathObj; | 133 if (pathObj != null) return pathObj; |
| 134 | 134 |
| 135 | |
| 136 final segments = new _PathParser().parse(path); | 135 final segments = new _PathParser().parse(path); |
| 137 if (segments == null) return _InvalidPropertyPath._instance; | 136 if (segments == null) return _InvalidPropertyPath._instance; |
| 138 | 137 |
| 139 // TODO(jmesserly): we could use an UnmodifiableListView here, but that adds | 138 // TODO(jmesserly): we could use an UnmodifiableListView here, but that adds |
| 140 // memory overhead. | 139 // memory overhead. |
| 141 pathObj = new PropertyPath._(segments.toList(growable: false)); | 140 pathObj = new PropertyPath._(segments.toList(growable: false)); |
| 142 if (_pathCache.length >= _pathCacheLimit) { | 141 if (_pathCache.length >= _pathCacheLimit) { |
| 143 _pathCache.remove(_pathCache.keys.first); | 142 _pathCache.remove(_pathCache.keys.first); |
| 144 } | 143 } |
| 145 _pathCache[path] = pathObj; | 144 _pathCache[path] = pathObj; |
| (...skipping 48 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 194 /// [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function | 193 /// [1]: http://en.wikipedia.org/wiki/Jenkins_hash_function |
| 195 // TODO(jmesserly): should reuse this instead, see | 194 // TODO(jmesserly): should reuse this instead, see |
| 196 // https://code.google.com/p/dart/issues/detail?id=11617 | 195 // https://code.google.com/p/dart/issues/detail?id=11617 |
| 197 int get hashCode { | 196 int get hashCode { |
| 198 int hash = 0; | 197 int hash = 0; |
| 199 for (int i = 0, len = _segments.length; i < len; i++) { | 198 for (int i = 0, len = _segments.length; i < len; i++) { |
| 200 hash = 0x1fffffff & (hash + _segments[i].hashCode); | 199 hash = 0x1fffffff & (hash + _segments[i].hashCode); |
| 201 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); | 200 hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); |
| 202 hash = hash ^ (hash >> 6); | 201 hash = hash ^ (hash >> 6); |
| 203 } | 202 } |
| 204 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); | 203 hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); |
| 205 hash = hash ^ (hash >> 11); | 204 hash = hash ^ (hash >> 11); |
| 206 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); | 205 return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); |
| 207 } | 206 } |
| 208 | 207 |
| 209 /// Returns the current value of the path from the provided [obj]ect. | 208 /// Returns the current value of the path from the provided [obj]ect. |
| 210 getValueFrom(Object obj) { | 209 getValueFrom(Object obj) { |
| 211 if (!isValid) return null; | 210 if (!isValid) return null; |
| 212 for (var segment in _segments) { | 211 for (var segment in _segments) { |
| 213 if (obj == null) return null; | 212 if (obj == null) return null; |
| 214 obj = _getObjectProperty(obj, segment); | 213 obj = _getObjectProperty(obj, segment); |
| (...skipping 23 matching lines...) Expand all Loading... | |
| 238 observe(obj, _segments[i]); | 237 observe(obj, _segments[i]); |
| 239 | 238 |
| 240 if (i >= last) break; | 239 if (i >= last) break; |
| 241 obj = _getObjectProperty(obj, _segments[i++]); | 240 obj = _getObjectProperty(obj, _segments[i++]); |
| 242 } | 241 } |
| 243 } | 242 } |
| 244 | 243 |
| 245 // Dart note: it doesn't make sense to have compiledGetValueFromFn in Dart. | 244 // Dart note: it doesn't make sense to have compiledGetValueFromFn in Dart. |
| 246 } | 245 } |
| 247 | 246 |
| 248 | |
| 249 /// Visible only for testing: | 247 /// Visible only for testing: |
| 250 getSegmentsOfPropertyPathForTesting(p) => p._segments; | 248 getSegmentsOfPropertyPathForTesting(p) => p._segments; |
| 251 | 249 |
| 252 class _InvalidPropertyPath extends PropertyPath { | 250 class _InvalidPropertyPath extends PropertyPath { |
| 253 static final _instance = new _InvalidPropertyPath(); | 251 static final _instance = new _InvalidPropertyPath(); |
| 254 | 252 |
| 255 bool get isValid => false; | 253 bool get isValid => false; |
| 256 _InvalidPropertyPath() : super._([]); | 254 _InvalidPropertyPath() : super._([]); |
| 257 } | 255 } |
| 258 | 256 |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 271 } | 269 } |
| 272 } else if (property is String) { | 270 } else if (property is String) { |
| 273 return object[property]; | 271 return object[property]; |
| 274 } else if (property is Symbol) { | 272 } else if (property is Symbol) { |
| 275 // Support indexer if available, e.g. Maps or polymer_expressions Scope. | 273 // Support indexer if available, e.g. Maps or polymer_expressions Scope. |
| 276 // This is the default syntax used by polymer/nodebind and | 274 // This is the default syntax used by polymer/nodebind and |
| 277 // polymer/observe-js PathObserver. | 275 // polymer/observe-js PathObserver. |
| 278 // TODO(sigmund): should we also support using checking dynamically for | 276 // TODO(sigmund): should we also support using checking dynamically for |
| 279 // whether the type practically implements the indexer API | 277 // whether the type practically implements the indexer API |
| 280 // (smoke.hasInstanceMethod(type, const Symbol('[]')))? | 278 // (smoke.hasInstanceMethod(type, const Symbol('[]')))? |
| 281 if (object is Indexable || object is Map && !_MAP_PROPERTIES.contains(proper ty)) { | 279 if (object is Indexable || |
| 280 object is Map && !_MAP_PROPERTIES.contains(property)) { | |
| 282 return object[smoke.symbolToName(property)]; | 281 return object[smoke.symbolToName(property)]; |
| 283 } | 282 } |
| 284 try { | 283 try { |
| 285 return smoke.read(object, property); | 284 return smoke.read(object, property); |
| 286 } on NoSuchMethodError catch (_) { | 285 } on NoSuchMethodError catch (_) { |
| 287 // Rethrow, unless the type implements noSuchMethod, in which case we | 286 // Rethrow, unless the type implements noSuchMethod, in which case we |
| 288 // interpret the exception as a signal that the method was not found. | 287 // interpret the exception as a signal that the method was not found. |
| 289 // Dart note: getting invalid properties is an error, unlike in JS where | 288 // Dart note: getting invalid properties is an error, unlike in JS where |
| 290 // it returns undefined. | 289 // it returns undefined. |
| 291 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; | 290 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; |
| 292 } | 291 } |
| 293 } | 292 } |
| 294 | 293 |
| 295 if (_logger.isLoggable(Level.FINER)) { | 294 if (_logger.isLoggable(Level.FINER)) { |
| 296 _logger.finer("can't get $property in $object"); | 295 _logger.finer("can't get $property in $object"); |
| 297 } | 296 } |
| 298 return null; | 297 return null; |
| 299 } | 298 } |
| 300 | 299 |
| 301 bool _setObjectProperty(object, property, value) { | 300 bool _setObjectProperty(object, property, value) { |
| 302 if (object == null) return false; | 301 if (object == null) return false; |
| 303 | 302 |
| 304 if (property is int) { | 303 if (property is int) { |
| 305 if (object is List && property >= 0 && property < object.length) { | 304 if (object is List && property >= 0 && property < object.length) { |
| 306 object[property] = value; | 305 object[property] = value; |
| 307 return true; | 306 return true; |
| 308 } | 307 } |
| 309 } else if (property is Symbol) { | 308 } else if (property is Symbol) { |
| 310 // Support indexer if available, e.g. Maps or polymer_expressions Scope. | 309 // Support indexer if available, e.g. Maps or polymer_expressions Scope. |
| 311 if (object is Indexable || object is Map && !_MAP_PROPERTIES.contains(proper ty)) { | 310 if (object is Indexable || |
| 311 object is Map && !_MAP_PROPERTIES.contains(property)) { | |
| 312 object[smoke.symbolToName(property)] = value; | 312 object[smoke.symbolToName(property)] = value; |
| 313 return true; | 313 return true; |
| 314 } | 314 } |
| 315 try { | 315 try { |
| 316 smoke.write(object, property, value); | 316 smoke.write(object, property, value); |
| 317 return true; | 317 return true; |
| 318 } on NoSuchMethodError catch (_) { | 318 } on NoSuchMethodError catch (_) { |
| 319 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; | 319 if (!smoke.hasNoSuchMethod(object.runtimeType)) rethrow; |
| 320 } | 320 } |
| 321 } | 321 } |
| (...skipping 14 matching lines...) Expand all Loading... | |
| 336 | 336 |
| 337 _isIdent(s) => _identRegExp.hasMatch(s); | 337 _isIdent(s) => _identRegExp.hasMatch(s); |
| 338 | 338 |
| 339 // Dart note: refactored to convert to codepoints once and operate on codepoints | 339 // Dart note: refactored to convert to codepoints once and operate on codepoints |
| 340 // rather than characters. | 340 // rather than characters. |
| 341 class _PathParser { | 341 class _PathParser { |
| 342 List keys = []; | 342 List keys = []; |
| 343 int index = -1; | 343 int index = -1; |
| 344 String key; | 344 String key; |
| 345 | 345 |
| 346 final Map<String, List<String>> _pathStateMachine = { | 346 final Map<String, Map<String, List<String>>> _pathStateMachine = { |
| 347 'beforePath': { | 347 'beforePath': { |
| 348 'ws': ['beforePath'], | 348 'ws': ['beforePath'], |
| 349 'ident': ['inIdent', 'append'], | 349 'ident': ['inIdent', 'append'], |
| 350 '[': ['beforeElement'], | 350 '[': ['beforeElement'], |
| 351 'eof': ['afterPath'] | 351 'eof': ['afterPath'] |
| 352 }, | 352 }, |
| 353 | |
| 354 'inPath': { | 353 'inPath': { |
| 355 'ws': ['inPath'], | 354 'ws': ['inPath'], |
| 356 '.': ['beforeIdent'], | 355 '.': ['beforeIdent'], |
| 357 '[': ['beforeElement'], | 356 '[': ['beforeElement'], |
| 358 'eof': ['afterPath'] | 357 'eof': ['afterPath'] |
| 359 }, | 358 }, |
| 360 | |
| 361 'beforeIdent': { | 359 'beforeIdent': { |
| 362 'ws': ['beforeIdent'], | 360 'ws': ['beforeIdent'], |
| 363 'ident': ['inIdent', 'append'] | 361 'ident': ['inIdent', 'append'] |
| 364 }, | 362 }, |
| 365 | |
| 366 'inIdent': { | 363 'inIdent': { |
| 367 'ident': ['inIdent', 'append'], | 364 'ident': ['inIdent', 'append'], |
| 368 '0': ['inIdent', 'append'], | 365 '0': ['inIdent', 'append'], |
| 369 'number': ['inIdent', 'append'], | 366 'number': ['inIdent', 'append'], |
| 370 'ws': ['inPath', 'push'], | 367 'ws': ['inPath', 'push'], |
| 371 '.': ['beforeIdent', 'push'], | 368 '.': ['beforeIdent', 'push'], |
| 372 '[': ['beforeElement', 'push'], | 369 '[': ['beforeElement', 'push'], |
| 373 'eof': ['afterPath', 'push'] | 370 'eof': ['afterPath', 'push'] |
| 374 }, | 371 }, |
| 375 | |
| 376 'beforeElement': { | 372 'beforeElement': { |
| 377 'ws': ['beforeElement'], | 373 'ws': ['beforeElement'], |
| 378 '0': ['afterZero', 'append'], | 374 '0': ['afterZero', 'append'], |
| 379 'number': ['inIndex', 'append'], | 375 'number': ['inIndex', 'append'], |
| 380 "'": ['inSingleQuote', 'append', ''], | 376 "'": ['inSingleQuote', 'append', ''], |
| 381 '"': ['inDoubleQuote', 'append', ''] | 377 '"': ['inDoubleQuote', 'append', ''] |
| 382 }, | 378 }, |
| 383 | |
| 384 'afterZero': { | 379 'afterZero': { |
| 385 'ws': ['afterElement', 'push'], | 380 'ws': ['afterElement', 'push'], |
| 386 ']': ['inPath', 'push'] | 381 ']': ['inPath', 'push'] |
| 387 }, | 382 }, |
| 388 | |
| 389 'inIndex': { | 383 'inIndex': { |
| 390 '0': ['inIndex', 'append'], | 384 '0': ['inIndex', 'append'], |
| 391 'number': ['inIndex', 'append'], | 385 'number': ['inIndex', 'append'], |
| 392 'ws': ['afterElement'], | 386 'ws': ['afterElement'], |
| 393 ']': ['inPath', 'push'] | 387 ']': ['inPath', 'push'] |
| 394 }, | 388 }, |
| 395 | |
| 396 'inSingleQuote': { | 389 'inSingleQuote': { |
| 397 "'": ['afterElement'], | 390 "'": ['afterElement'], |
| 398 'eof': ['error'], | 391 'eof': ['error'], |
| 399 'else': ['inSingleQuote', 'append'] | 392 'else': ['inSingleQuote', 'append'] |
| 400 }, | 393 }, |
| 401 | |
| 402 'inDoubleQuote': { | 394 'inDoubleQuote': { |
| 403 '"': ['afterElement'], | 395 '"': ['afterElement'], |
| 404 'eof': ['error'], | 396 'eof': ['error'], |
| 405 'else': ['inDoubleQuote', 'append'] | 397 'else': ['inDoubleQuote', 'append'] |
| 406 }, | 398 }, |
| 407 | |
| 408 'afterElement': { | 399 'afterElement': { |
| 409 'ws': ['afterElement'], | 400 'ws': ['afterElement'], |
| 410 ']': ['inPath', 'push'] | 401 ']': ['inPath', 'push'] |
| 411 } | 402 } |
| 412 }; | 403 }; |
| 413 | 404 |
| 414 /// From getPathCharType: determines the type of a given [code]point. | 405 /// From getPathCharType: determines the type of a given [code]point. |
| 415 String _getPathCharType(code) { | 406 String _getPathCharType(code) { |
| 416 if (code == null) return 'eof'; | 407 if (code == null) return 'eof'; |
| 417 switch(code) { | 408 switch (code) { |
| 418 case 0x5B: // [ | 409 case 0x5B: // [ |
| 419 case 0x5D: // ] | 410 case 0x5D: // ] |
| 420 case 0x2E: // . | 411 case 0x2E: // . |
| 421 case 0x22: // " | 412 case 0x22: // " |
| 422 case 0x27: // ' | 413 case 0x27: // ' |
| 423 case 0x30: // 0 | 414 case 0x30: // 0 |
| 424 return _char(code); | 415 return _char(code); |
| 425 | 416 |
| 426 case 0x5F: // _ | 417 case 0x5F: // _ |
| 427 case 0x24: // $ | 418 case 0x24: // $ |
| 428 return 'ident'; | 419 return 'ident'; |
| 429 | 420 |
| 430 case 0x20: // Space | 421 case 0x20: // Space |
| 431 case 0x09: // Tab | 422 case 0x09: // Tab |
| 432 case 0x0A: // Newline | 423 case 0x0A: // Newline |
| 433 case 0x0D: // Return | 424 case 0x0D: // Return |
| 434 case 0xA0: // No-break space | 425 case 0xA0: // No-break space |
| 435 case 0xFEFF: // Byte Order Mark | 426 case 0xFEFF: // Byte Order Mark |
| 436 case 0x2028: // Line Separator | 427 case 0x2028: // Line Separator |
| 437 case 0x2029: // Paragraph Separator | 428 case 0x2029: // Paragraph Separator |
| 438 return 'ws'; | 429 return 'ws'; |
| 439 } | 430 } |
| 440 | 431 |
| 441 // a-z, A-Z | 432 // a-z, A-Z |
| 442 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) | 433 if ((0x61 <= code && code <= 0x7A) || (0x41 <= code && code <= 0x5A)) |
| 443 return 'ident'; | 434 return 'ident'; |
| 444 | 435 |
| 445 // 1-9 | 436 // 1-9 |
| 446 if (0x31 <= code && code <= 0x39) | 437 if (0x31 <= code && code <= 0x39) return 'number'; |
| 447 return 'number'; | |
| 448 | 438 |
| 449 return 'else'; | 439 return 'else'; |
| 450 } | 440 } |
| 451 | 441 |
| 452 static String _char(int codepoint) => new String.fromCharCodes([codepoint]); | 442 static String _char(int codepoint) => new String.fromCharCodes([codepoint]); |
| 453 | 443 |
| 454 void push() { | 444 void push() { |
| 455 if (key == null) return; | 445 if (key == null) return; |
| 456 | 446 |
| 457 // Dart note: we store the keys with different types, rather than | 447 // Dart note: we store the keys with different types, rather than |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 475 if ((mode == 'inSingleQuote' && nextChar == "'") || | 465 if ((mode == 'inSingleQuote' && nextChar == "'") || |
| 476 (mode == 'inDoubleQuote' && nextChar == '"')) { | 466 (mode == 'inDoubleQuote' && nextChar == '"')) { |
| 477 index++; | 467 index++; |
| 478 append(nextChar); | 468 append(nextChar); |
| 479 return true; | 469 return true; |
| 480 } | 470 } |
| 481 return false; | 471 return false; |
| 482 } | 472 } |
| 483 | 473 |
| 484 /// Returns the parsed keys, or null if there was a parse error. | 474 /// Returns the parsed keys, or null if there was a parse error. |
| 485 List<String> parse(String path) { | 475 List parse(String path) { |
|
Bob Nystrom
2016/01/22 18:50:25
So the old return type was straight up wrong, righ
vsm
2016/01/22 19:05:05
Yes, the list has Symbols and Strings (at least st
| |
| 486 var codePoints = stringToCodepoints(path); | 476 var codePoints = stringToCodepoints(path); |
| 487 var mode = 'beforePath'; | 477 var mode = 'beforePath'; |
| 488 | 478 |
| 489 while (mode != null) { | 479 while (mode != null) { |
| 490 index++; | 480 index++; |
| 491 var c = index >= codePoints.length ? null : codePoints[index]; | 481 var c = index >= codePoints.length ? null : codePoints[index]; |
| 492 | 482 |
| 493 if (c != null && | 483 if (c != null && |
| 494 _char(c) == '\\' && _maybeUnescapeQuote(mode, codePoints)) continue; | 484 _char(c) == '\\' && |
| 485 _maybeUnescapeQuote(mode, codePoints)) continue; | |
| 495 | 486 |
| 496 var type = _getPathCharType(c); | 487 var type = _getPathCharType(c); |
| 497 if (mode == 'error') return null; | 488 if (mode == 'error') return null; |
| 498 | 489 |
| 499 var typeMap = _pathStateMachine[mode]; | 490 var typeMap = _pathStateMachine[mode]; |
| 500 var transition = typeMap[type]; | 491 var transition = typeMap[type]; |
| 501 if (transition == null) transition = typeMap['else']; | 492 if (transition == null) transition = typeMap['else']; |
| 502 if (transition == null) return null; // parse error; | 493 if (transition == null) return null; // parse error; |
| 503 | 494 |
| 504 mode = transition[0]; | 495 mode = transition[0]; |
| 505 var actionName = transition.length > 1 ? transition[1] : null; | 496 var actionName = transition.length > 1 ? transition[1] : null; |
| 506 if (actionName == 'push' && key != null) push(); | 497 if (actionName == 'push' && key != null) push(); |
| 507 if (actionName == 'append') { | 498 if (actionName == 'append') { |
| 508 var newChar = transition.length > 2 && transition[2] != null | 499 var newChar = transition.length > 2 && transition[2] != null |
| 509 ? transition[2] : _char(c); | 500 ? transition[2] |
| 501 : _char(c); | |
| 510 append(newChar); | 502 append(newChar); |
| 511 } | 503 } |
| 512 | 504 |
| 513 if (mode == 'afterPath') return keys; | 505 if (mode == 'afterPath') return keys; |
| 514 } | 506 } |
| 515 return null; // parse error | 507 return null; // parse error |
| 516 } | 508 } |
| 517 } | 509 } |
| 518 | 510 |
| 519 final Logger _logger = new Logger('observe.PathObserver'); | 511 final Logger _logger = new Logger('observe.PathObserver'); |
| 520 | 512 |
| 521 | |
| 522 /// This is a simple cache. It's like LRU but we don't update an item on a | 513 /// This is a simple cache. It's like LRU but we don't update an item on a |
| 523 /// cache hit, because that would require allocation. Better to let it expire | 514 /// cache hit, because that would require allocation. Better to let it expire |
| 524 /// and reallocate the PropertyPath. | 515 /// and reallocate the PropertyPath. |
| 525 // TODO(jmesserly): this optimization is from observe-js, how valuable is it in | 516 // TODO(jmesserly): this optimization is from observe-js, how valuable is it in |
| 526 // practice? | 517 // practice? |
| 527 final _pathCache = new LinkedHashMap<String, PropertyPath>(); | 518 final _pathCache = new LinkedHashMap<String, PropertyPath>(); |
| 528 | 519 |
| 529 /// The size of a path like "foo.bar" is approximately 160 bytes, so this | 520 /// The size of a path like "foo.bar" is approximately 160 bytes, so this |
| 530 /// reserves ~16Kb of memory for recently used paths. Since paths are frequently | 521 /// reserves ~16Kb of memory for recently used paths. Since paths are frequently |
| 531 /// reused, the theory is that this ends up being a good tradeoff in practice. | 522 /// reused, the theory is that this ends up being a good tradeoff in practice. |
| (...skipping 107 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 639 bool _check({bool skipChanges: false}) { | 630 bool _check({bool skipChanges: false}) { |
| 640 bool changed = false; | 631 bool changed = false; |
| 641 _value.length = _observed.length ~/ 2; | 632 _value.length = _observed.length ~/ 2; |
| 642 var oldValues = null; | 633 var oldValues = null; |
| 643 for (var i = 0; i < _observed.length; i += 2) { | 634 for (var i = 0; i < _observed.length; i += 2) { |
| 644 var object = _observed[i]; | 635 var object = _observed[i]; |
| 645 var path = _observed[i + 1]; | 636 var path = _observed[i + 1]; |
| 646 var value; | 637 var value; |
| 647 if (identical(object, _observerSentinel)) { | 638 if (identical(object, _observerSentinel)) { |
| 648 var observable = path as Bindable; | 639 var observable = path as Bindable; |
| 649 value = _state == _Observer._UNOPENED ? | 640 value = _state == _Observer._UNOPENED |
| 650 observable.open((_) => this.deliver()) : | 641 ? observable.open((_) => this.deliver()) |
| 651 observable.value; | 642 : observable.value; |
| 652 } else { | 643 } else { |
| 653 value = (path as PropertyPath).getValueFrom(object); | 644 value = (path as PropertyPath).getValueFrom(object); |
| 654 } | 645 } |
| 655 | 646 |
| 656 if (skipChanges) { | 647 if (skipChanges) { |
| 657 _value[i ~/ 2] = value; | 648 _value[i ~/ 2] = value; |
| 658 continue; | 649 continue; |
| 659 } | 650 } |
| 660 | 651 |
| 661 if (value == _value[i ~/ 2]) continue; | 652 if (value == _value[i ~/ 2]) continue; |
| (...skipping 18 matching lines...) Expand all Loading... | |
| 680 } | 671 } |
| 681 | 672 |
| 682 /// An object accepted by [PropertyPath] where properties are read and written | 673 /// An object accepted by [PropertyPath] where properties are read and written |
| 683 /// as indexing operations, just like a [Map]. | 674 /// as indexing operations, just like a [Map]. |
| 684 abstract class Indexable<K, V> { | 675 abstract class Indexable<K, V> { |
| 685 V operator [](K key); | 676 V operator [](K key); |
| 686 operator []=(K key, V value); | 677 operator []=(K key, V value); |
| 687 } | 678 } |
| 688 | 679 |
| 689 const _observerSentinel = const _ObserverSentinel(); | 680 const _observerSentinel = const _ObserverSentinel(); |
| 690 class _ObserverSentinel { const _ObserverSentinel(); } | 681 |
| 682 class _ObserverSentinel { | |
| 683 const _ObserverSentinel(); | |
| 684 } | |
| 691 | 685 |
| 692 // Visible for testing | 686 // Visible for testing |
| 693 get observerSentinelForTesting => _observerSentinel; | 687 get observerSentinelForTesting => _observerSentinel; |
| 694 | 688 |
| 695 // A base class for the shared API implemented by PathObserver and | 689 // A base class for the shared API implemented by PathObserver and |
| 696 // CompoundObserver and used in _ObservedSet. | 690 // CompoundObserver and used in _ObservedSet. |
| 697 abstract class _Observer extends Bindable { | 691 abstract class _Observer extends Bindable { |
| 698 Function _notifyCallback; | 692 Function _notifyCallback; |
| 699 int _notifyArgumentCount; | 693 int _notifyArgumentCount; |
| 700 var _value; | 694 var _value; |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 757 var cycles = 0; | 751 var cycles = 0; |
| 758 while (cycles < _MAX_DIRTY_CHECK_CYCLES && _check()) { | 752 while (cycles < _MAX_DIRTY_CHECK_CYCLES && _check()) { |
| 759 cycles++; | 753 cycles++; |
| 760 } | 754 } |
| 761 return cycles > 0; | 755 return cycles > 0; |
| 762 } | 756 } |
| 763 | 757 |
| 764 void _report(newValue, oldValue, [extraArg]) { | 758 void _report(newValue, oldValue, [extraArg]) { |
| 765 try { | 759 try { |
| 766 switch (_notifyArgumentCount) { | 760 switch (_notifyArgumentCount) { |
| 767 case 0: _notifyCallback(); break; | 761 case 0: |
| 768 case 1: _notifyCallback(newValue); break; | 762 _notifyCallback(); |
| 769 case 2: _notifyCallback(newValue, oldValue); break; | 763 break; |
| 770 case 3: _notifyCallback(newValue, oldValue, extraArg); break; | 764 case 1: |
| 765 _notifyCallback(newValue); | |
| 766 break; | |
| 767 case 2: | |
| 768 _notifyCallback(newValue, oldValue); | |
| 769 break; | |
| 770 case 3: | |
| 771 _notifyCallback(newValue, oldValue, extraArg); | |
| 772 break; | |
| 771 } | 773 } |
| 772 } catch (e, s) { | 774 } catch (e, s) { |
| 773 // Deliver errors async, so if a single callback fails it doesn't prevent | 775 // Deliver errors async, so if a single callback fails it doesn't prevent |
| 774 // other things from working. | 776 // other things from working. |
| 775 new Completer().completeError(e, s); | 777 new Completer().completeError(e, s); |
| 776 } | 778 } |
| 777 } | 779 } |
| 778 } | 780 } |
| 779 | 781 |
| 780 /// The observedSet abstraction is a perf optimization which reduces the total | 782 /// The observedSet abstraction is a perf optimization which reduces the total |
| (...skipping 115 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 896 } | 898 } |
| 897 } else { | 899 } else { |
| 898 // TODO(sigmund): consider adding object to MapChangeRecord, and make | 900 // TODO(sigmund): consider adding object to MapChangeRecord, and make |
| 899 // this more precise. | 901 // this more precise. |
| 900 return false; | 902 return false; |
| 901 } | 903 } |
| 902 } | 904 } |
| 903 return true; | 905 return true; |
| 904 } | 906 } |
| 905 | 907 |
| 906 void _callback(records) { | 908 void _callback(List<ChangeRecord> records) { |
| 907 if (_canIgnoreRecords(records)) return; | 909 if (_canIgnoreRecords(records)) return; |
| 908 for (var observer in _observers.toList(growable: false)) { | 910 for (var observer in _observers.toList(growable: false)) { |
| 909 if (observer._isOpen) observer._iterateObjects(observe); | 911 if (observer._isOpen) observer._iterateObjects(observe); |
| 910 } | 912 } |
| 911 | 913 |
| 912 for (var observer in _observers.toList(growable: false)) { | 914 for (var observer in _observers.toList(growable: false)) { |
| 913 if (observer._isOpen) observer._check(); | 915 if (observer._isOpen) observer._check(); |
| 914 } | 916 } |
| 915 } | 917 } |
| 916 } | 918 } |
| 917 | 919 |
| 918 const int _MAX_DIRTY_CHECK_CYCLES = 1000; | 920 const int _MAX_DIRTY_CHECK_CYCLES = 1000; |
| OLD | NEW |