|
401 | 401 | };
|
402 | 402 | }
|
403 | 403 |
|
| 404 | + /* |
| 405 | + * The observedSet abstraction is a perf optimization which reduces the total |
| 406 | + * number of Object.observe observations of a set of objects. The idea is that |
| 407 | + * groups of Observers will have some object dependencies in common and this |
| 408 | + * observed set ensures that each object in the transitive closure of |
| 409 | + * dependencies is only observed once. The observedSet acts as a write barrier |
| 410 | + * such that whenever any change comes through, all Observers are checked for |
| 411 | + * changed values. |
| 412 | + * |
| 413 | + * Note that this optimization is explicitly moving work from setup-time to |
| 414 | + * change-time. |
| 415 | + * |
| 416 | + * TODO(rafaelw): Implement "garbage collection". In order to move work off |
| 417 | + * the critical path, when Observers are closed, their observed objects are |
| 418 | + * not Object.unobserve(d). As a result, it's possible that if the observedSet |
| 419 | + * is kept open, but some Observers have been closed, it could case "leaks" |
| 420 | + * (reprevent otherwise collectable objects from being collected). At some |
| 421 | + * point, we should implement incremental "gc" which keeps a list of |
| 422 | + * observedSets which may need clean-up and does small amounts of cleanup on a |
| 423 | + * timeout until all is clean. |
| 424 | + */ |
| 425 | + |
404 | 426 | function getObservedObject(observer, object, arrayObserve) {
|
405 | 427 | var dir = observedObjectCache.pop() || newObservedObject();
|
406 | 428 | dir.open(observer);
|
407 | 429 | dir.observe(object, arrayObserve);
|
408 | 430 | return dir;
|
409 | 431 | }
|
410 | 432 |
|
411 |
| - var emptyArray = []; |
412 | 433 | var observedSetCache = [];
|
413 | 434 |
|
414 | 435 | function newObservedSet() {
|
415 |
| - var observers = []; |
416 | 436 | var observerCount = 0;
|
| 437 | + var observers = []; |
417 | 438 | var objects = [];
|
418 |
| - var toRemove = emptyArray; |
419 |
| - var resetNeeded = false; |
420 |
| - var resetScheduled = false; |
421 | 439 |
|
422 | 440 | function observe(obj) {
|
423 | 441 | if (!obj)
|
424 | 442 | return;
|
425 | 443 |
|
426 |
| - var index = toRemove.indexOf(obj); |
427 |
| - if (index >= 0) { |
428 |
| - toRemove[index] = undefined; |
429 |
| - objects.push(obj); |
430 |
| - } else if (objects.indexOf(obj) < 0) { |
| 444 | + if (objects.indexOf(obj) < 0) { |
431 | 445 | objects.push(obj);
|
432 | 446 | Object.observe(obj, callback);
|
433 | 447 | }
|
434 | 448 |
|
435 | 449 | observe(Object.getPrototypeOf(obj));
|
436 | 450 | }
|
437 | 451 |
|
438 |
| - function reset() { |
439 |
| - var objs = toRemove === emptyArray ? [] : toRemove; |
440 |
| - toRemove = objects; |
441 |
| - objects = objs; |
442 |
| - |
443 |
| - var observer; |
444 |
| - for (var id in observers) { |
445 |
| - observer = observers[id]; |
446 |
| - if (!observer || observer.state_ != OPENED) |
447 |
| - continue; |
448 |
| - |
449 |
| - observer.iterateObjects_(observe); |
450 |
| - } |
451 |
| - |
452 |
| - for (var i = 0; i < toRemove.length; i++) { |
453 |
| - var obj = toRemove[i]; |
454 |
| - if (obj) |
455 |
| - Object.unobserve(obj, callback); |
456 |
| - } |
457 |
| - |
458 |
| - toRemove.length = 0; |
459 |
| - } |
460 |
| - |
461 |
| - function scheduledReset() { |
462 |
| - resetScheduled = false; |
463 |
| - if (!resetNeeded) |
464 |
| - return; |
465 |
| - |
466 |
| - reset(); |
467 |
| - } |
468 |
| - |
469 |
| - function scheduleReset() { |
470 |
| - if (resetScheduled) |
471 |
| - return; |
472 |
| - |
473 |
| - resetNeeded = true; |
474 |
| - resetScheduled = true; |
475 |
| - runEOM(scheduledReset); |
476 |
| - } |
477 |
| - |
478 | 452 | function callback() {
|
479 |
| - reset(); |
480 |
| - |
481 | 453 | var observer;
|
| 454 | + for (var i = 0; i < observers.length; i++) { |
| 455 | + observer = observers[i]; |
| 456 | + if (observer.state_ == OPENED) { |
| 457 | + observer.iterateObjects_(observe); |
| 458 | + } |
| 459 | + } |
482 | 460 |
|
483 |
| - for (var id in observers) { |
484 |
| - observer = observers[id]; |
485 |
| - if (!observer || observer.state_ != OPENED) |
486 |
| - continue; |
487 |
| - |
488 |
| - observer.check_(); |
| 461 | + for (var i = 0; i < observers.length; i++) { |
| 462 | + observer = observers[i]; |
| 463 | + if (observer.state_ == OPENED) { |
| 464 | + observer.check_(); |
| 465 | + } |
489 | 466 | }
|
490 | 467 | }
|
491 | 468 |
|
492 | 469 | var record = {
|
493 | 470 | object: undefined,
|
494 | 471 | objects: objects,
|
495 | 472 | open: function(obs) {
|
496 |
| - observers[obs.id_] = obs; |
| 473 | + observers.push(obs); |
497 | 474 | observerCount++;
|
498 | 475 | obs.iterateObjects_(observe);
|
499 | 476 | },
|
500 | 477 | close: function(obs) {
|
501 |
| - var anyLeft = false; |
502 |
| - |
503 |
| - observers[obs.id_] = undefined; |
504 | 478 | observerCount--;
|
505 |
| - |
506 |
| - if (observerCount) { |
507 |
| - scheduleReset(); |
| 479 | + if (observerCount > 0) { |
508 | 480 | return;
|
509 | 481 | }
|
510 |
| - resetNeeded = false; |
511 | 482 |
|
512 | 483 | for (var i = 0; i < objects.length; i++) {
|
513 | 484 | Object.unobserve(objects[i], callback);
|
|
517 | 488 | observers.length = 0;
|
518 | 489 | objects.length = 0;
|
519 | 490 | observedSetCache.push(this);
|
520 |
| - }, |
521 |
| - reset: scheduleReset |
| 491 | + } |
522 | 492 | };
|
523 | 493 |
|
524 | 494 | return record;
|
|
901 | 871 | }
|
902 | 872 | }
|
903 | 873 |
|
904 |
| - if (this.directObserver_) { |
905 |
| - if (needsDirectObserver) { |
906 |
| - this.directObserver_.reset(); |
907 |
| - return; |
908 |
| - } |
909 |
| - this.directObserver_.close(); |
910 |
| - this.directObserver_ = undefined; |
911 |
| - return; |
912 |
| - } |
913 |
| - |
914 | 874 | if (needsDirectObserver)
|
915 | 875 | this.directObserver_ = getObservedSet(this, object);
|
916 | 876 | },
|
917 | 877 |
|
918 |
| - closeObservers_: function() { |
| 878 | + disconnect_: function() { |
919 | 879 | for (var i = 0; i < this.observed_.length; i += 2) {
|
920 | 880 | if (this.observed_[i] === observerSentinel)
|
921 | 881 | this.observed_[i + 1].close();
|
922 | 882 | }
|
923 | 883 | this.observed_.length = 0;
|
924 |
| - }, |
925 |
| - |
926 |
| - disconnect_: function() { |
927 |
| - this.value_ = undefined; |
| 884 | + this.value_.length = 0; |
928 | 885 |
|
929 | 886 | if (this.directObserver_) {
|
930 | 887 | this.directObserver_.close(this);
|
931 | 888 | this.directObserver_ = undefined;
|
932 | 889 | }
|
933 |
| - |
934 |
| - this.closeObservers_(); |
935 | 890 | },
|
936 | 891 |
|
937 | 892 | addPath: function(object, path) {
|
|
954 | 909 | throw Error('Can only reset while open');
|
955 | 910 |
|
956 | 911 | this.state_ = RESETTING;
|
957 |
| - this.closeObservers_(); |
| 912 | + this.disconnect_(); |
958 | 913 | },
|
959 | 914 |
|
960 | 915 | finishReset: function() {
|
|
0 commit comments