Skip to content

Commit

Permalink
Refactoring meta, part 2
Browse files Browse the repository at this point in the history
This improves encapsulation of the inheritedMap and inheritedMapOfMaps
meta properties, and changes their internal implementations to avoid
Object.create and some potential shape de-opts.

I'm seeing single digit percentage improvement in initial render speed.
  • Loading branch information
ef4 committed Aug 22, 2015
1 parent 12d23bc commit b799c3b
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 154 deletions.
12 changes: 4 additions & 8 deletions packages/ember-metal/lib/dependent_keys.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,16 @@ import {
export function addDependentKeys(desc, obj, keyName, meta) {
// the descriptor has a list of dependent keys, so
// add all of its dependent keys.
var idx, len, depKey, keys;
var idx, len, depKey;
var depKeys = desc._dependentKeys;
if (!depKeys) {
return;
}

for (idx = 0, len = depKeys.length; idx < len; idx++) {
depKey = depKeys[idx];
// Lookup keys meta for depKey
keys = meta.writableDeps(depKey);
// Increment the number of times depKey depends on keyName.
keys[keyName] = (keys[keyName] || 0) + 1;
meta.writeDeps(depKey, keyName, (meta.peekDeps(depKey, keyName)|| 0) + 1);
// Watch the depKey
watch(obj, depKey, meta);
}
Expand All @@ -41,17 +39,15 @@ export function removeDependentKeys(desc, obj, keyName, meta) {
// the descriptor has a list of dependent keys, so
// remove all of its dependent keys.
var depKeys = desc._dependentKeys;
var idx, len, depKey, keys;
var idx, len, depKey;
if (!depKeys) {
return;
}

for (idx = 0, len = depKeys.length; idx < len; idx++) {
depKey = depKeys[idx];
// Lookup keys meta for depKey
keys = meta.writableDeps(depKey);
// Decrement the number of times depKey depends on keyName.
keys[keyName] = (keys[keyName] || 0) - 1;
meta.writeDeps(depKey, keyName, (meta.peekDeps(depKey, keyName) || 0) - 1);
// Unwatch the depKey
unwatch(obj, depKey, meta);
}
Expand Down
132 changes: 89 additions & 43 deletions packages/ember-metal/lib/meta.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
//
'REMOVE_USE_STRICT: true';

import isEnabled from 'ember-metal/features';
import { protoMethods as listenerMethods } from 'ember-metal/meta_listeners';
import EmptyObject from 'ember-metal/empty_object';

Expand All @@ -23,11 +22,11 @@ import EmptyObject from 'ember-metal/empty_object';
The following methods will get generated metaprogrammatically, and
I'm including them here for greppability:
writableCache, readableCache, writableWatching, readableWatching,
peekWatching, clearWatching, writableMixins, readableMixins,
peekMixins, clearMixins, writableBindings, readableBindings,
peekBindings, clearBindings, writableValues, readableValues,
peekValues, clearValues, writableDeps, readableDeps, getAllDeps
writableCache, readableCache, writeWatching,
peekWatching, clearWatching, writeMixins,
peekMixins, clearMixins, writeBindings,
peekBindings, clearBindings, writeValues,
peekValues, clearValues, writeDeps, forEachInDeps
writableChainWatchers, readableChainWatchers, writableChains,
readableChains
Expand Down Expand Up @@ -100,77 +99,131 @@ function inheritedMap(name, Meta) {
let key = memberProperty(name);
let capitalized = capitalize(name);

Meta.prototype['writable' + capitalized] = function() {
return this._getOrCreateInheritedMap(key);
Meta.prototype['write' + capitalized] = function(subkey, value) {
let map = this._getOrCreateOwnMap(key);
map[subkey] = value;
};

Meta.prototype['readable' + capitalized] = function() {
return this._getInherited(key);
Meta.prototype['peek' + capitalized] = function(subkey) {
return this._findInherited(key, subkey);
};

Meta.prototype['peek' + capitalized] = function(subkey) {
let map = this._getInherited(key);
if (map) {
return map[subkey];
Meta.prototype['forEach' + capitalized] = function(fn) {
let pointer = this;
let seen = new EmptyObject();
while (pointer !== undefined) {
let map = pointer[key];
if (map) {
Object.keys(map).forEach(key => {
if (!seen[key]) {
seen[key] = true;
fn(key, map[key]);
}
});
}
pointer = pointer.parent;
}
};

Meta.prototype['clear' + capitalized] = function() {
this[key] = new EmptyObject();
};

Meta.prototype['deleteFrom' + capitalized] = function(subkey) {
delete this._getOrCreateOwnMap(key)[subkey];
};

Meta.prototype['hasIn' + capitalized] = function(subkey) {
return this._findInherited(key, subkey) !== undefined;
};
}

Meta.prototype._getOrCreateInheritedMap = function(key) {
let ret = this[key];
if (!ret) {
if (this.parent) {
ret = this[key] = Object.create(this.parent._getOrCreateInheritedMap(key));
} else {
ret = this[key] = new EmptyObject();
Meta.prototype._getInherited = function(key) {
let pointer = this;
while (pointer !== undefined) {
if (pointer[key]) {
return pointer[key];
}
pointer = pointer.parent;
}
return ret;
};

Meta.prototype._getInherited = function(key) {
Meta.prototype._findInherited = function(key, subkey) {
let pointer = this;
while (pointer !== undefined) {
if (pointer[key]) {
return pointer[key];
let map = pointer[key];
if (map) {
let value = map[subkey];
if (value !== undefined) {
return value;
}
}
pointer = pointer.parent;
}
};


// Implements a member that provides a lazily created map of maps,
// with inheritance at both levels.
function inheritedMapOfMaps(name, Meta) {
let key = memberProperty(name);
let capitalized = capitalize(name);

Meta.prototype['writable' + capitalized] = function(subkey) {
let outerMap = this._getOrCreateInheritedMap(key);
Meta.prototype['write' + capitalized] = function(subkey, itemkey, value) {
let outerMap = this._getOrCreateOwnMap(key);
let innerMap = outerMap[subkey];
if (!innerMap) {
innerMap = outerMap[subkey] = new EmptyObject();
} else if (!Object.hasOwnProperty.call(outerMap, subkey)) {
innerMap = outerMap[subkey] = Object.create(innerMap);
}
return innerMap;
innerMap[itemkey] = value;
};

Meta.prototype['readable' + capitalized] = function(subkey) {
let map = this._getInherited(key);
if (map) {
return map[subkey];
Meta.prototype['peek' + capitalized] = function(subkey, itemkey) {
let pointer = this;
while (pointer !== undefined) {
let map = pointer[key];
if (map) {
let value = map[subkey];
if (value) {
if (value[itemkey] !== undefined) {
return value[itemkey];
}
}
}
pointer = pointer.parent;
}
};

Meta.prototype['getAll' + capitalized] = function() {
return this._getInherited(key);
Meta.prototype['has' + capitalized] = function(subkey) {
let map = this._getInherited(key);
return map && !!map[subkey];
};

Meta.prototype['forEachIn' + capitalized] = function(subkey, fn) {
return this._forEachIn(key, subkey, fn);
};
}

Meta.prototype._forEachIn = function(key, subkey, fn) {
let pointer = this;
let seen = new EmptyObject();
while (pointer !== undefined) {
let map = pointer[key];
if (map) {
let innerMap = map[subkey];
if (innerMap) {
Object.keys(innerMap).forEach(key => {
if (!seen[key]) {
seen[key] = true;
fn(key, innerMap[key]);
}
});
}
}
pointer = pointer.parent;
}
};

// Implements a member that provides a non-heritable, lazily-created
// object using the method you provide.
function ownCustomObject(name, Meta) {
Expand Down Expand Up @@ -236,10 +289,6 @@ var EMBER_META_PROPERTY = {
// Placeholder for non-writable metas.
export var EMPTY_META = new Meta(null);

if (isEnabled('mandatory-setter')) {
EMPTY_META.writableValues();
}

/**
Retrieves the meta hash for an object. If `writable` is true ensures the
hash is writable for this object as well.
Expand Down Expand Up @@ -270,9 +319,6 @@ export function meta(obj, writable) {

if (!ret) {
ret = new Meta(obj);
if (isEnabled('mandatory-setter')) {
ret.writableValues();
}
} else {
ret = new Meta(obj, ret);
}
Expand Down
59 changes: 23 additions & 36 deletions packages/ember-metal/lib/mixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,6 @@ ROOT.__hasSuper = false;
var REQUIRED;
var a_slice = [].slice;

function mixinsMeta(obj) {
return metaFor(obj, true).writableMixins();
}

function isMethod(obj) {
return 'function' === typeof obj &&
obj.isMethod !== false &&
Expand All @@ -67,8 +63,8 @@ function mixinProperties(mixinsMeta, mixin) {

if (mixin instanceof Mixin) {
guid = guidFor(mixin);
if (mixinsMeta[guid]) { return CONTINUE; }
mixinsMeta[guid] = mixin;
if (mixinsMeta.peekMixins(guid)) { return CONTINUE; }
mixinsMeta.writeMixins(guid, mixin);
return mixin.properties;
} else {
return mixin; // apply anonymous mixin properties
Expand Down Expand Up @@ -269,7 +265,7 @@ var IS_BINDING = /^.+Binding$/;

function detectBinding(obj, key, value, m) {
if (IS_BINDING.test(key)) {
m.writableBindings()[key] = value;
m.writeBindings(key, value);
}
}

Expand Down Expand Up @@ -300,29 +296,24 @@ function connectStreamBinding(obj, key, stream) {

function connectBindings(obj, m) {
// TODO Mixin.apply(instance) should disconnect binding if exists
var bindings = m.readableBindings();
var key, binding, to;
if (bindings) {
for (key in bindings) {
binding = bindings[key];
if (binding) {
to = key.slice(0, -7); // strip Binding off end
if (isStream(binding)) {
connectStreamBinding(obj, to, binding);
continue;
} else if (binding instanceof Binding) {
binding = binding.copy(); // copy prototypes' instance
binding.to(to);
} else { // binding is string path
binding = new Binding(to, binding);
}
binding.connect(obj);
obj[key] = binding;
m.forEachBindings((key, binding) => {
if (binding) {
let to = key.slice(0, -7); // strip Binding off end
if (isStream(binding)) {
connectStreamBinding(obj, to, binding);
return;
} else if (binding instanceof Binding) {
binding = binding.copy(); // copy prototypes' instance
binding.to(to);
} else { // binding is string path
binding = new Binding(to, binding);
}
binding.connect(obj);
obj[key] = binding;
}
// mark as applied
m.clearBindings();
}
});
// mark as applied
m.clearBindings();
}

function finishPartial(obj, m) {
Expand Down Expand Up @@ -390,7 +381,7 @@ function applyMixin(obj, mixins, partial) {
// * Set up _super wrapping if necessary
// * Set up computed property descriptors
// * Copying `toString` in broken browsers
mergeMixins(mixins, mixinsMeta(obj), descs, values, obj, keys);
mergeMixins(mixins, metaFor(obj), descs, values, obj, keys);

for (var i = 0, l = keys.length; i < l; i++) {
key = keys[i];
Expand Down Expand Up @@ -656,17 +647,13 @@ MixinPrototype.keys = function() {
// TODO: Make Ember.mixin
Mixin.mixins = function(obj) {
var m = obj['__ember_meta__'];
var mixins = m && m.readableMixins();
var ret = [];
if (!m) { return ret; }

if (!mixins) { return ret; }

for (var key in mixins) {
var currentMixin = mixins[key];

m.forEachMixins((key, currentMixin) => {
// skip primitive mixins since these are always anonymous
if (!currentMixin.properties) { ret.push(currentMixin); }
}
});

return ret;
};
Expand Down
2 changes: 1 addition & 1 deletion packages/ember-metal/lib/properties.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function defineProperty(obj, keyName, desc, data, meta) {

if (isEnabled('mandatory-setter')) {
if (watching) {
meta.writableValues()[keyName] = data;
meta.writeValues(keyName, data);
Object.defineProperty(obj, keyName, {
configurable: true,
enumerable: true,
Expand Down
Loading

0 comments on commit b799c3b

Please sign in to comment.