From 2f63cab2640d3f138ae41a902968cbcb42b089d8 Mon Sep 17 00:00:00 2001 From: Kris Selden Date: Sun, 19 Jul 2015 21:13:38 -0700 Subject: [PATCH] [CLEANUP beta] Abstract chainWatchers into an object. --- packages/ember-metal/lib/chains.js | 210 +++++++++++------- packages/ember-metal/lib/computed.js | 13 +- packages/ember-metal/lib/property_events.js | 45 +--- .../ember-metal/tests/watching/watch_test.js | 12 +- 4 files changed, 143 insertions(+), 137 deletions(-) diff --git a/packages/ember-metal/lib/chains.js b/packages/ember-metal/lib/chains.js index 8e2ad585d30..693de378a56 100644 --- a/packages/ember-metal/lib/chains.js +++ b/packages/ember-metal/lib/chains.js @@ -21,6 +21,93 @@ function Chains() { } Chains.prototype = Object.create(null); +function ChainWatchers(obj) { + // this obj would be the referencing chain node's parent node's value + this.obj = obj; + // chain nodes that reference a key in this obj by key + // we only create ChainWatchers when we are going to add them + // so create this upfront + this.chains = new Chains(); +} + +ChainWatchers.prototype = { + add(key, node) { + let nodes = this.chains[key]; + if (nodes === undefined) { + this.chains[key] = [node]; + } else { + nodes.push(node); + } + }, + + remove(key, node) { + let nodes = this.chains[key]; + if (nodes) { + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i] === node) { + nodes.splice(i, 1); + break; + } + } + } + }, + + has(key, node) { + let nodes = this.chains[key]; + if (nodes) { + for (var i = 0, l = nodes.length; i < l; i++) { + if (nodes[i] === node) { + return true; + } + } + } + return false; + }, + + revalidateAll() { + for (let key in this.chains) { + this.notify(key, true, undefined); + } + }, + + revalidate(key) { + this.notify(key, true, undefined); + }, + + // key: the string key that is part of a path changed + // revalidate: boolean the chains that are watching this value should revalidate + // callback: function that will be called with the the object and path that + // will be/are invalidated by this key change depending on the + // whether the revalidate flag is passed + notify(key, revalidate, callback) { + let nodes = this.chains[key]; + if (nodes === undefined || nodes.length === 0) { + return; + } + + let affected; + + if (callback) { + affected = []; + } + + for (let i = 0, l = nodes.length; i < l; i++) { + nodes[i].notify(revalidate, affected); + } + + if (callback === undefined) { + return; + } + + // we gather callbacks so we don't notify them during revalidation + for (let i = 0, l = affected.length; i < l; i += 2) { + let obj = affected[i]; + let path = affected[i + 1]; + callback(obj, path); + } + } +}; + var pendingQueue = []; // attempts to add the pendingQueue chains again. If some of them end up @@ -49,19 +136,13 @@ function addChainWatcher(obj, keyName, node) { return; } - var m = metaFor(obj); - var nodes = m.chainWatchers; + let m = metaFor(obj); - // TODO remove hasOwnProperty check - if (nodes === undefined || !m.hasOwnProperty('chainWatchers')) { - nodes = m.chainWatchers = new Chains(); + if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { + m.chainWatchers = new ChainWatchers(obj); } - if (!nodes[keyName]) { - nodes[keyName] = [node]; - } else { - nodes[keyName].push(node); - } + m.chainWatchers.add(keyName, node); watchKey(obj, keyName, m); } @@ -71,20 +152,18 @@ function removeChainWatcher(obj, keyName, node) { return; } - var m = obj['__ember_meta__']; - if (m && !m.hasOwnProperty('chainWatchers')) { return; } // nothing to do - - var nodes = m && m.chainWatchers; + let m = obj.__ember_meta__; - if (nodes && nodes[keyName]) { - nodes = nodes[keyName]; - for (var i = 0, l = nodes.length; i < l; i++) { - if (nodes[i] === node) { - nodes.splice(i, 1); - break; - } - } + if (!m || + m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { + return; } + + // make meta writable + m = metaFor(obj); + + m.chainWatchers.remove(keyName, node); + unwatchKey(obj, keyName, m); } @@ -287,44 +366,8 @@ ChainNode.prototype = { } }, - willChange(events) { - var chains = this._chains; - var node; - if (chains) { - for (var key in chains) { - node = chains[key]; - if (node !== undefined) { - node.willChange(events); - } - } - } - - if (this._parent) { - this._parent.notifyChainChange(this, this._key, 1, events); - } - }, - - notifyChainChange(chain, path, depth, events) { - if (this._key) { - path = this._key + '.' + path; - } - - if (this._parent) { - this._parent.notifyChainChange(this, path, depth + 1, events); - } else { - if (depth > 1) { - events.push(this.value(), path); - } - path = 'this.' + path; - if (this._paths[path] > 0) { - events.push(this.value(), path); - } - } - }, - - didChange(events) { - // invalidate my own value first. - if (this._watching) { + notify(revalidate, affected) { + if (revalidate && this._watching) { var obj = this._parent.value(); if (obj !== this._object) { removeChainWatcher(this._object, this._key, this); @@ -347,48 +390,49 @@ ChainNode.prototype = { for (var key in chains) { node = chains[key]; if (node !== undefined) { - node.didChange(events); + node.notify(revalidate, affected); } } } - // if no events are passed in then we only care about the above wiring update - if (events === null) { - return; + if (affected && this._parent) { + this._parent.populateAffected(this, this._key, 1, affected); + } + }, + + populateAffected(chain, path, depth, affected) { + if (this._key) { + path = this._key + '.' + path; } - // and finally tell parent about my path changing... if (this._parent) { - this._parent.notifyChainChange(this, this._key, 1, events); + this._parent.populateAffected(this, path, depth + 1, affected); + } else { + if (depth > 1) { + affected.push(this.value(), path); + } + path = 'this.' + path; + if (this._paths[path] > 0) { + affected.push(this.value(), path); + } } } }; export function finishChains(obj) { // We only create meta if we really have to - var m = obj['__ember_meta__']; - var chains, chainWatchers, chainNodes; - + let m = obj.__ember_meta__; if (m) { // finish any current chains node watchers that reference obj - chainWatchers = m.chainWatchers; + let chainWatchers = m.chainWatchers; if (chainWatchers) { - for (var key in chainWatchers) { - chainNodes = chainWatchers[key]; - if (chainNodes) { - for (var i = 0, l = chainNodes.length; i < l; i++) { - var node = chainNodes[i]; - if (node) { - node.didChange(null); - } - } - } - } + chainWatchers.revalidateAll(); } // copy chains from prototype - chains = m.chains; + let chains = m.chains; if (chains && chains.value() !== obj) { - metaFor(obj).chains = chains = chains.copy(obj); + // need to check if meta is writable + metaFor(obj).chains = chains.copy(obj); } } } diff --git a/packages/ember-metal/lib/computed.js b/packages/ember-metal/lib/computed.js index 5af59e95a9a..d16a8b675ba 100644 --- a/packages/ember-metal/lib/computed.js +++ b/packages/ember-metal/lib/computed.js @@ -274,12 +274,6 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) { } }; -function finishChains(chainNodes) { - for (var i = 0, l = chainNodes.length; i < l; i++) { - chainNodes[i].didChange(null); - } -} - /** Access the value of the function backing the computed property. If this property has already been cached, return the cached result. @@ -308,7 +302,7 @@ function finishChains(chainNodes) { @public */ ComputedPropertyPrototype.get = function(obj, keyName) { - var ret, cache, meta, chainNodes; + var ret, cache, meta; if (this._cacheable) { meta = metaFor(obj); cache = meta.cache; @@ -332,9 +326,8 @@ ComputedPropertyPrototype.get = function(obj, keyName) { cache[keyName] = ret; } - chainNodes = meta.chainWatchers && meta.chainWatchers[keyName]; - if (chainNodes) { - finishChains(chainNodes); + if (meta.chainWatchers) { + meta.chainWatchers.revalidate(keyName); } addDependentKeys(this, obj, keyName, meta); } else { diff --git a/packages/ember-metal/lib/property_events.js b/packages/ember-metal/lib/property_events.js index 496de3de8b6..f46505f4f50 100644 --- a/packages/ember-metal/lib/property_events.js +++ b/packages/ember-metal/lib/property_events.js @@ -191,53 +191,26 @@ function iterDeps(method, obj, deps, depKey, seen, meta) { } function chainsWillChange(obj, keyName, m) { - if (m.chainWatchers === undefined || - !m.hasOwnProperty('chainWatchers')) { + if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { return; } - var nodes = m.chainWatchers[keyName]; - if (nodes === undefined) { - return; - } - var events = []; - var i, l; - - for (i = 0, l = nodes.length; i < l; i++) { - nodes[i].willChange(events); - } - for (i = 0, l = events.length; i < l; i += 2) { - propertyWillChange(events[i], events[i + 1]); - } + m.chainWatchers.notify(keyName, false, propertyWillChange); } -function chainsDidChange(obj, keyName, m, suppressEvents) { - if (m.chainWatchers === undefined || - !m.hasOwnProperty('chainWatchers')) { - return; - } - var nodes = m.chainWatchers[keyName]; - if (nodes === undefined) { +function chainsDidChange(obj, keyName, m) { + if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { return; } - var events = suppressEvents ? null : []; - var i, l; - for (i = 0, l = nodes.length; i < l; i++) { - nodes[i].didChange(events); - } - - if (suppressEvents) { - return; - } - - for (i = 0, l = events.length; i < l; i += 2) { - propertyDidChange(events[i], events[i + 1]); - } + m.chainWatchers.notify(keyName, true, propertyDidChange); } function overrideChains(obj, keyName, m) { - chainsDidChange(obj, keyName, m, true); + if (m.chainWatchers === undefined || m.chainWatchers.obj !== obj) { + return; + } + m.chainWatchers.revalidate(keyName); } /** diff --git a/packages/ember-metal/tests/watching/watch_test.js b/packages/ember-metal/tests/watching/watch_test.js index 70b3a4a5a67..910a9d8e9c6 100644 --- a/packages/ember-metal/tests/watching/watch_test.js +++ b/packages/ember-metal/tests/watching/watch_test.js @@ -204,16 +204,14 @@ QUnit.test('when watching a global object, destroy should remove chain watchers var meta_Global = Ember.meta(Global); var chainNode = Ember.meta(obj).chains._chains.Global._chains.foo; - var index = meta_Global.chainWatchers.foo.indexOf(chainNode); equal(meta_Global.watching.foo, 1, 'should be watching foo'); - strictEqual(meta_Global.chainWatchers.foo[index], chainNode, 'should have chain watcher'); + equal(meta_Global.chainWatchers.has('foo', chainNode), true, 'should have chain watcher'); destroy(obj); - index = meta_Global.chainWatchers.foo.indexOf(chainNode); equal(meta_Global.watching.foo, 0, 'should not be watching foo'); - equal(index, -1, 'should not have chain watcher'); + equal(meta_Global.chainWatchers.has('foo', chainNode), false, 'should not have chain watcher'); lookup['Global'] = Global = null; // reset }); @@ -228,16 +226,14 @@ QUnit.test('when watching another object, destroy should remove chain watchers f var meta_objB = Ember.meta(objB); var chainNode = Ember.meta(objA).chains._chains.b._chains.foo; - var index = meta_objB.chainWatchers.foo.indexOf(chainNode); equal(meta_objB.watching.foo, 1, 'should be watching foo'); - strictEqual(meta_objB.chainWatchers.foo[index], chainNode, 'should have chain watcher'); + equal(meta_objB.chainWatchers.has('foo', chainNode), true, 'should have chain watcher'); destroy(objA); - index = meta_objB.chainWatchers.foo.indexOf(chainNode); equal(meta_objB.watching.foo, 0, 'should not be watching foo'); - equal(index, -1, 'should not have chain watcher'); + equal(meta_objB.chainWatchers.has('foo', chainNode), false, 'should not have chain watcher'); }); // TESTS for length property