Skip to content

Commit

Permalink
feat(improved-cache): makes the adapter/serializer instance cache fas…
Browse files Browse the repository at this point in the history
…ter.

Reduces the number of lookups, sets, expense of detection, allocations for fallbacks. Also includes additional internalModel cleanup and backburner/state machine optimizations, and deprecates store.lookupAdater and store.lookupSerializer
  • Loading branch information
runspired committed Oct 19, 2016
1 parent 5afa263 commit 8a4876c
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 100 deletions.
92 changes: 43 additions & 49 deletions addon/-private/system/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import Ember from 'ember';
import Model from 'ember-data/model';
import { instrument, assert, warn, runInDebug } from "ember-data/-private/debug";
import { instrument, assert, deprecate, warn, runInDebug } from "ember-data/-private/debug";
import _normalizeLink from "ember-data/-private/system/normalize-link";
import normalizeModelName from "ember-data/-private/system/normalize-model-name";
import { InvalidError } from 'ember-data/adapters/errors';
Expand Down Expand Up @@ -97,7 +97,6 @@ const {
normalize,
peekAll,
peekRecord,
retrieveManagedInstance,
serializerFor,
typeMapFor,
typeMapFor_allocate
Expand All @@ -116,7 +115,6 @@ const {
'normalize',
'peekAll',
'peekRecord',
'retrieveManagedInstance',
'serializerFor',
'typeMapFor',
'typeMapFor_allocate'
Expand Down Expand Up @@ -210,11 +208,31 @@ Store = Service.extend({
store: this
});
this._pendingSave = [];
this._instanceCache = new ContainerInstanceCache(getOwner(this));

let fallbackLookup = (namespace, modelName) => { return this._fallbacksFor(namespace, modelName); };
this._instanceCache = new ContainerInstanceCache(getOwner(this), this, fallbackLookup);
//Used to keep track of all the find requests that need to be coalesced
this._pendingFetch = Map.create();
},

_fallbacksFor(namespace, modelName) {
assert(`You must pass namespace to _fallbacksFor`, namespace);
assert(`You must pass modelName to _fallbacksFor`, modelName);

if (namespace === 'adapter') {
return ['application', this.get('adapter'), '-json-api'];
}
if (namespace === 'serializer') {
return [
'application',
this.adapterFor(modelName).get('defaultSerializer'),
'-default'
];
}

throw new Error(`Unable to find fallbacks for unknown namespace ${namespace}. (provided modelName: ${modelName})`);
},

/**
The adapter to use to communicate to a backend server or other persistence layer.
Expand Down Expand Up @@ -267,13 +285,11 @@ Store = Service.extend({
@return DS.Adapter
*/
defaultAdapter: Ember.computed('adapter', function() {
var adapter = get(this, 'adapter');
let adapter = get(this, 'adapter');

assert('You tried to set `adapter` property to an instance of `DS.Adapter`, where it should be a name', typeof adapter === 'string');

adapter = this.retrieveManagedInstance('adapter', adapter);

return adapter;
return this.adapterFor(adapter);
}),

// .....................
Expand Down Expand Up @@ -2455,7 +2471,9 @@ Store = Service.extend({
assert("You need to pass a model name to the store's adapterFor method", isPresent(modelName));
assert(`Passing classes to store.adapterFor has been removed. Please pass a dasherized string instead of ${Ember.inspect(modelName)}`, typeof modelName === 'string');

return this.lookupAdapter(modelName);
let normalizedModelName = normalizeModelName(modelName);

return this._instanceCache.get('adapter', normalizedModelName);
},

_adapterRun(fn) {
Expand Down Expand Up @@ -2492,60 +2510,36 @@ Store = Service.extend({
assert("You need to pass a model name to the store's serializerFor method", isPresent(modelName));
assert(`Passing classes to store.serializerFor has been removed. Please pass a dasherized string instead of ${Ember.inspect(modelName)}`, typeof modelName === 'string');

var fallbacks = [
'application',
this.adapterFor(modelName).get('defaultSerializer'),
'-default'
];
let normalizedModelName = normalizeModelName(modelName);

var serializer = this.lookupSerializer(modelName, fallbacks);
return serializer;
},

/**
Retrieve a particular instance from the
container cache. If not found, creates it and
placing it in the cache.
Enabled a store to manage local instances of
adapters and serializers.
@method retrieveManagedInstance
@private
@param {String} modelName the object modelName
@param {String} name the object name
@param {Array} fallbacks the fallback objects to lookup if the lookup for modelName or 'application' fails
@return {Ember.Object}
*/
retrieveManagedInstance(type, modelName, fallbacks) {
heimdall.increment(retrieveManagedInstance);
var normalizedModelName = normalizeModelName(modelName);

var instance = this._instanceCache.get(type, normalizedModelName, fallbacks);
set(instance, 'store', this);
return instance;
return this._instanceCache.get('serializer', normalizedModelName);
},

lookupAdapter(name) {
return this.retrieveManagedInstance('adapter', name, this.get('_adapterFallbacks'));
deprecate(`Use of lookupAdapter is deprecated, use adapterFor instead.`, {
id: 'ember-data:lookupAdapter',
since: '2.10',
until: '3.0'
});
return this.adapterFor(name);
},

_adapterFallbacks: Ember.computed('adapter', function() {
var adapter = this.get('adapter');
return ['application', adapter, '-json-api'];
}),

lookupSerializer(name, fallbacks) {
return this.retrieveManagedInstance('serializer', name, fallbacks);
lookupSerializer(name) {
deprecate(`Use of lookupSerializer is deprecated, use serializerFor instead.`, {
id: 'ember-data:lookupSerializer',
since: '2.10',
until: '3.0'
});
return this.serializerFor(name);
},

willDestroy() {
this._super(...arguments);
this.recordArrayManager.destroy();
this._instanceCache.destroy();

this.unloadAll();
}

});

function deserializeRecordId(store, key, relationship, id) {
Expand Down
116 changes: 66 additions & 50 deletions addon/-private/system/store/container-instance-cache.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
/* global heimdall */
import Ember from 'ember';
import EmptyObject from "ember-data/-private/system/empty-object";
const assign = Ember.assign || Ember.merge;
const { set } = Ember;

const {
__get,
_instanceFor
} = heimdall.registerMonitor('system.store.container-instance-cache',
'__get',
'_instanceFor'
);

/*
* The `ContainerInstanceCache` serves as a lazy cache for looking up
Expand All @@ -12,71 +20,74 @@ const assign = Ember.assign || Ember.merge;
* when the preferred lookup fails. For example, say you try to look up `adapter:post`,
* but there is no entry (app/adapters/post.js in EmberCLI) for `adapter:post` in the registry.
*
* The `fallbacks` array passed will then be used; the first entry in the fallbacks array
* that exists in the container will then be cached for `adapter:post`. So, the next time you
* look up `adapter:post`, you'll get the `adapter:application` instance (or whatever the fallback
* was if `adapter:application` doesn't exist).
* When an adapter or serializer is unfound, getFallbacks will be invoked with the current namespace
* ('adapter' or 'serializer') and the 'preferredKey' (usually a modelName). The method should return
* an array of keys to check against.
*
* The first entry in the fallbacks array that exists in the container will then be cached for
* `adapter:post`. So, the next time you look up `adapter:post`, you'll get the `adapter:application`
* instance (or whatever the fallback was if `adapter:application` doesn't exist).
*
* @private
* @class ContainerInstanceCache
*
*/
export default function ContainerInstanceCache(owner) {
this._owner = owner;
this._cache = new EmptyObject();
}
export default class ContainerInstanceCache {
constructor(owner, store, getFallbacks) {
this._owner = owner;
this._store = store;
this._namespaces = {
adapter: new EmptyObject(),
serializer: new EmptyObject()
};
this._getFallbacks = getFallbacks;
}

const {
__get,
instanceFor
} = heimdall.registerMonitor('system.store.container-instance-cache',
'get',
'instanceFor'
);
get(namespace, preferredKey) {
heimdall.increment(__get);
let cache = this._namespaces[namespace];

ContainerInstanceCache.prototype = new EmptyObject();
if (cache[preferredKey]) {
return cache[preferredKey];
}

assign(ContainerInstanceCache.prototype, {
get(type, preferredKey, fallbacks) {
heimdall.increment(__get);
let cache = this._cache;
let preferredLookupKey = `${type}:${preferredKey}`;
let preferredLookupKey = `${namespace}:${preferredKey}`;

if (!(preferredLookupKey in cache)) {
let instance = this.instanceFor(preferredLookupKey) || this._findInstance(type, fallbacks);
if (instance) {
cache[preferredLookupKey] = instance;
}
let instance = this._instanceFor(preferredLookupKey) || this._findInstance(namespace, this._getFallbacks(namespace, preferredKey));
if (instance) {
cache[preferredKey] = instance;
set(instance, 'store', this._store);
}
return cache[preferredLookupKey];
},

_findInstance(type, fallbacks) {
return cache[preferredKey];
}

_findInstance(namespace, fallbacks) {
let cache = this._namespaces[namespace];

for (let i = 0, length = fallbacks.length; i < length; i++) {
let fallback = fallbacks[i];
let lookupKey = `${type}:${fallback}`;
let instance = this.instanceFor(lookupKey);

if (instance) {
return instance;
if (cache[fallback]) {
return cache[fallback];
}
}
},

instanceFor(key) {
heimdall.increment(instanceFor);
let cache = this._cache;
if (!cache[key]) {
let instance = this._owner.lookup(key);
let lookupKey = `${namespace}:${fallback}`;
let instance = this._instanceFor(lookupKey);

if (instance) {
cache[key] = instance;
cache[fallback] = instance;
return instance;
}
}
return cache[key];
},
}

destroy() {
let cache = this._cache;
_instanceFor(key) {
heimdall.increment(_instanceFor);
return this._owner.lookup(key);
}

destroyCache(cache) {
let cacheEntries = Object.keys(cache);

for (let i = 0, length = cacheEntries.length; i < length; i++) {
Expand All @@ -86,12 +97,17 @@ assign(ContainerInstanceCache.prototype, {
cacheEntry.destroy();
}
}
this._owner = null;
},
}

constructor: ContainerInstanceCache,
destroy() {
this.destroyCache(this._namespaces.adapter);
this.destroyCache(this._namespaces.serializer);
this._namespaces = null;
this._store = null;
this._owner = null;
}

toString() {
return 'ContainerInstanceCache';
}
});
}
2 changes: 1 addition & 1 deletion tests/helpers/store.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default function setupStore(options) {
return env;
}

export {setupStore};
export { setupStore };

export function createStore(options) {
return setupStore(options).store;
Expand Down

0 comments on commit 8a4876c

Please sign in to comment.