Skip to content

Commit

Permalink
move responsibility for lazy setup into computed.js entirely instead …
Browse files Browse the repository at this point in the history
…of guarding setup outside
  • Loading branch information
krisselden committed Jul 7, 2012
1 parent b57ed3e commit 6a17956
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 86 deletions.
1 change: 0 additions & 1 deletion benchmarks/suites/views/each_view.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ after(function() {

bench("creating and appending a new view with each", function() {
Ember.run(function() {
App.set('list', newContent());
view = App.View.create().append();
});
});
Expand Down
162 changes: 89 additions & 73 deletions packages/ember-metal/lib/computed.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ require('ember-metal/core');
require('ember-metal/platform');
require('ember-metal/utils');
require('ember-metal/properties');
require('ember-metal/watching');


Ember.warn("Computed properties will soon be cacheable by default. To enable this in your app, set `ENV.CP_DEFAULT_CACHEABLE = true`.", Ember.CP_DEFAULT_CACHEABLE);
Expand All @@ -18,7 +19,18 @@ var get = Ember.get,
metaFor = Ember.meta,
guidFor = Ember.guidFor,
a_slice = [].slice,
o_create = Ember.create;
o_create = Ember.create,
META_KEY = Ember.META_KEY,
watch = Ember.watch,
unwatch = Ember.unwatch,
isWatching = Ember.isWatching;

function hasCachedValue(obj, key) {
var meta, cache;
return (meta = obj[META_KEY]) &&
(cache = meta.cache) &&
(key in cache);
}

// ..........................................................
// DEPENDENT KEYS
Expand All @@ -39,9 +51,28 @@ var get = Ember.get,
This function returns a map of unique dependencies for a
given object and key.
*/
function uniqDeps(obj, depKey) {
var meta = metaFor(obj), deps = meta.deps, ret;
function keysForDep(obj, depsMeta, depKey) {
var keys = depsMeta[depKey];
if (!keys) {
// if there are no dependencies yet for a the given key
// create a new empty list of dependencies for the key
keys = depsMeta[depKey] = { __emberproto__: obj };
} else if (keys.__emberproto__ !== obj) {
// otherwise if the dependency list is inherited from
// a superclass, clone the hash
keys = depsMeta[depKey] = o_create(keys);
keys.__emberproto__ = obj;
}
return keys;
}

/**
@private
return obj[META_KEY].deps
*/
function metaForDeps(obj, meta) {
var deps = meta.deps;
// If the current object has no dependencies...
if (!deps) {
// initialize the dependencies with a pointer back to
Expand All @@ -53,66 +84,51 @@ function uniqDeps(obj, depKey) {
deps = meta.deps = o_create(deps);
deps.__emberproto__ = obj;
}

ret = deps[depKey];

if (!ret) {
// if there are no dependencies yet for a the given key
// create a new empty list of dependencies for the key
ret = deps[depKey] = { __emberproto__: obj };
} else if (ret.__emberproto__ !== obj) {
// otherwise if the dependency list is inherited from
// a superclass, clone the hash
ret = deps[depKey] = o_create(ret);
ret.__emberproto__ = obj;
}

return ret;
}

/**
@private
This method allows us to register that a particular
property depends on another property.
*/
function addDependentKey(obj, keyName, depKey) {
var deps = uniqDeps(obj, depKey);

// Increment the number of times depKey depends on
// keyName.
deps[keyName] = (deps[keyName] || 0) + 1;

// Watch the depKey
Ember.watch(obj, depKey);
return deps;
}

/**
@private
This method removes a dependency.
*/
function removeDependentKey(obj, keyName, depKey) {
var deps = uniqDeps(obj, depKey);

// Decrement the number of times depKey depends
// on keyName.
deps[keyName] = (deps[keyName] || 0) - 1;
/** @private */
function addDependentKeys(desc, obj, keyName, meta) {
// the descriptor has a list of dependent keys, so
// add all of its dependent keys.
var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
if (!depKeys || meta.setup[keyName]) return;

depsMeta = metaForDeps(obj, meta);

for(idx = 0, len = depKeys.length; idx < len; idx++) {
depKey = depKeys[idx];
// Lookup keys meta for depKey
keys = keysForDep(obj, depsMeta, depKey);
// Increment the number of times depKey depends on keyName.
keys[keyName] = (keys[keyName] || 0) + 1;
// Watch the depKey
watch(obj, depKey);
}

// Unwatch the depKey
Ember.unwatch(obj, depKey);
meta.setup[keyName] = true;
}

/** @private */
function addDependentKeys(desc, obj, keyName) {
function removeDependentKeys(desc, obj, keyName, meta) {
// the descriptor has a list of dependent keys, so
// add all of its dependent keys.
var keys = desc._dependentKeys,
len = keys ? keys.length : 0;

for(var idx=0; idx<len; idx++) {
addDependentKey(obj, keyName, keys[idx]);
var depKeys = desc._dependentKeys, depsMeta, idx, len, depKey, keys;
if (!depKeys || !meta.setup[keyName]) return;

depsMeta = metaForDeps(obj, meta);

for(idx = 0, len = depKeys.length; idx < len; idx++) {
depKey = depKeys[idx];
// Lookup keys meta for depKey
keys = keysForDep(obj, depsMeta, depKey);
// Increment the number of times depKey depends on keyName.
keys[keyName] = (keys[keyName] || 0) - 1;
// Watch the depKey
unwatch(obj, depKey);
}

meta.setup[keyName] = false;
}

// ..........................................................
Expand Down Expand Up @@ -237,7 +253,13 @@ ComputedPropertyPrototype.meta = function(meta) {

/** @private - impl descriptor API */
ComputedPropertyPrototype.willWatch = function(obj, keyName) {
this.setup(obj, keyName);
addDependentKeys(this, obj, keyName, metaFor(obj));
};

ComputedPropertyPrototype.didUnwatch = function(obj, keyName) {
if (!hasCachedValue(obj, keyName)) {
removeDependentKeys(this, obj, keyName, metaFor(obj));
}
};

/** @private - impl descriptor API */
Expand All @@ -249,13 +271,14 @@ ComputedPropertyPrototype.didChange = function(obj, keyName) {

/** @private - impl descriptor API */
ComputedPropertyPrototype.get = function(obj, keyName) {
var ret, cache;
var ret, cache, meta;

if (this._cacheable) {
cache = metaFor(obj).cache;
meta = metaFor(obj);
cache = meta.cache;
if (keyName in cache) { return cache[keyName]; }
ret = cache[keyName] = this.func.call(obj, keyName);
this.setup(obj, keyName);
addDependentKeys(this, obj, keyName, meta);
} else {
ret = this.func.call(obj, keyName);
}
Expand All @@ -277,35 +300,28 @@ ComputedPropertyPrototype.set = function(obj, keyName, value) {
ret = this.func.call(obj, keyName, value);
if (cacheable) {
meta.cache[keyName] = ret;
this.setup(obj, keyName);
addDependentKeys(this, obj, keyName, meta);
}
if (watched) { Ember.propertyDidChange(obj, keyName); }
this._suspended = oldSuspended;
return ret;
};

/** @private - called when property is defined */
ComputedPropertyPrototype.setup = function(obj, keyName) {
var meta = metaFor(obj);
if (!meta.setup[keyName]) {
meta.setup[keyName] = true;
addDependentKeys(this, obj, keyName);
if (isWatching(obj, keyName)) {
addDependentKeys(this, obj, keyName, metaFor(obj));
}
};

/** @private - impl descriptor API */
/** @private - called before property is overridden */
ComputedPropertyPrototype.teardown = function(obj, keyName) {
var keys = this._dependentKeys,
len = keys ? keys.length : 0,
meta = metaFor(obj);
var meta = metaFor(obj);

for(var idx=0; idx < len; idx++) {
removeDependentKey(obj, keyName, keys[idx]);
}
removeDependentKeys(this, obj, keyName, meta);

if (this._cacheable) { delete meta.cache[keyName]; }

meta.setup[keyName] = false;

return null; // no value to restore
};

Expand Down Expand Up @@ -352,7 +368,7 @@ Ember.computed = function(func) {
to return
*/
Ember.cacheFor = function(obj, key) {
Ember.cacheFor = function cacheFor(obj, key) {
var cache = metaFor(obj, false).cache;

if (cache && key in cache) {
Expand Down
20 changes: 11 additions & 9 deletions packages/ember-metal/lib/mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,8 @@ function finishPartial(obj, m) {
/** @private */
function applyMixin(obj, mixins, partial) {
var descs = {}, values = {}, m = Ember.meta(obj), req = m.required,
watching, paths, len, idx,
key, prevDesc, prevValue, value, desc;
key, value, desc,
prevDesc, prevValue, paths, len, idx;

// Go through all mixins and hashes passed in, and:
//
Expand Down Expand Up @@ -234,12 +234,6 @@ function applyMixin(obj, mixins, partial) {

if (desc === undefined && value === undefined) { continue; }

watching = m.watching[key];
// if a watched desc is overriding an existing one
if (watching && desc && (prevDesc = m.descs[key])) {
prevDesc.teardown(obj, key);
}

prevValue = obj[key];

if ('function' === typeof prevValue) {
Expand All @@ -257,12 +251,20 @@ function applyMixin(obj, mixins, partial) {
}

detectBinding(obj, key, value, m);

// Ember.defineProperty
prevDesc = m.descs[key];
if (prevDesc && !(key in Object.prototype)) {
prevDesc.teardown(obj, key);
}

m.descs[key] = desc;
obj[key] = value;

if (watching && desc) {
if (desc) {
desc.setup(obj, key);
}
// Ember.defineProperty

if ('function' === typeof value) {
if (paths = value.__ember_observesBefore__) {
Expand Down
5 changes: 3 additions & 2 deletions packages/ember-metal/lib/watching.js
Original file line number Diff line number Diff line change
Expand Up @@ -446,8 +446,9 @@ Ember.watch = function(obj, keyName) {
return this;
};

Ember.isWatching = function(obj, keyName) {
return !!metaFor(obj).watching[keyName];
Ember.isWatching = function isWatching(obj, key) {
var meta = obj[META_KEY];
return (meta && meta.watching[key]) > 0;
};

Ember.watch.flushPending = flushPendingChains;
Expand Down
4 changes: 3 additions & 1 deletion packages/ember-metal/tests/watching/isWatching_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,14 +45,16 @@ test("isWatching is true for chained observers", function() {
test("isWatching is true for computed properties", function() {
testObserver(function(obj, key, fn) {
Ember.defineProperty(obj, 'computed', Ember.computed(fn).property(key));
Ember.watch(obj, 'computed');
}, function(obj, key, fn) {
Ember.defineProperty(obj, 'computed', null);
});
});

test("isWatching is true for computed properties", function() {
test("isWatching is true for chained computed properties", function() {
testObserver(function(obj, key, fn) {
Ember.defineProperty(obj, 'computed', Ember.computed(fn).property(key + '.bar'));
Ember.watch(obj, 'computed');
}, function(obj, key, fn) {
Ember.defineProperty(obj, 'computed', null);
});
Expand Down

0 comments on commit 6a17956

Please sign in to comment.