Skip to content

Commit a64bf46

Browse files
committed
Implement alternate storage strategies.
The following storage strategies exist and are used from the `Stack` implementation: * `ListCache` * `Hash` * `MapCache` `ListCache` =========== This is the initial storage mechanism. The underlying data format is as follows: ```js [ [key, value] [key, value] [key, value] [key, value] ] ``` `Hash` ====== The underlying data format is as follows: ```js { [key]: value } ``` The base object is an `EmptyObject` (roughly `Object.create(null)`). `MapCache` ========== `MapCache` defers to different underlying storage mechanisms based on the key being stored: * `string` -- Utilizes a `Hash` dedicated to string keys. * `number` / `symbol` / `boolean` -- Utilizes a dedicated `Hash` for these types (all are stored in the same `Hash`). * Everything else falls back to using `ListCache`. --- The overall strategy is to store items in the `ListCache` until it has 200 items in it, then we translate from `ListCache` to `MapCache` (which actually uses 3 different structures internally depending on which object type is being used as a key). All credit to this mechanism goes to Lodash, they did incredible work here and we are just utilizing their mechanisms. Once the performance of this within Ember is validated, we will migrate away from this locally vendored version of this file and simply use a stripped version of `lodash` itself from the build.
1 parent 9cef7a3 commit a64bf46

File tree

1 file changed

+165
-11
lines changed

1 file changed

+165
-11
lines changed

packages/ember-metal/lib/utils/lodash-stack.js

Lines changed: 165 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1+
// jshint eqeqeq:false
2+
// jshint laxbreak:true
3+
4+
import EmptyObject from '../empty_object';
5+
16
/*
2-
From Lodash's private ListCache here:
7+
From Lodash's private Stack/Hash/MapCache/ListCache here:
38
49
https://github.com/lodash/lodash/blob/4.13.1/dist/lodash.js#L1790-L1900
510
@@ -16,6 +21,24 @@
1621
***********************************************************************
1722
1823
*/
24+
25+
const LARGE_ARRAY_SIZE = 200;
26+
const HASH_UNDEFINED = '__lodash_hash_undefined__';
27+
28+
function isKeyable(value) {
29+
var type = typeof value;
30+
return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
31+
? (value !== '__proto__')
32+
: (value === null);
33+
}
34+
35+
function getMapData(map, key) {
36+
var data = map.__data__;
37+
return isKeyable(key)
38+
? data[typeof key == 'string' ? 'string' : 'hash']
39+
: data.map;
40+
}
41+
1942
function assocIndexOf(array, key) {
2043
var length = array.length;
2144
while (length--) {
@@ -27,9 +50,108 @@ function assocIndexOf(array, key) {
2750
return -1;
2851
}
2952

30-
export default class ListCache {
31-
constructor() {
32-
this.__data__ = [];
53+
export class Hash {
54+
constructor(entries) {
55+
let index = -1;
56+
let length = entries ? entries.length : 0;
57+
58+
this.clear();
59+
60+
while (++index < length) {
61+
let entry = entries[index];
62+
this.set(entry[0], entry[1]);
63+
}
64+
}
65+
66+
clear() {
67+
this.__data__ = new EmptyObject();
68+
}
69+
70+
delete(key) {
71+
return this.has(key) && delete this.__data__[key];
72+
}
73+
74+
get(key) {
75+
let data = this.__data__;
76+
let result = data[key];
77+
78+
return result === HASH_UNDEFINED ? undefined : result;
79+
}
80+
81+
has(key) {
82+
let data = this.__data__;
83+
return data[key] !== undefined;
84+
}
85+
86+
set(key, value) {
87+
var data = this.__data__;
88+
data[key] = value === undefined ? HASH_UNDEFINED : value;
89+
}
90+
91+
forEach(callback) {
92+
let data = this.__data__;
93+
for (let key in data) {
94+
callback(key, data[key]);
95+
}
96+
}
97+
}
98+
99+
export class MapCache {
100+
constructor(entries) {
101+
let index = -1;
102+
let length = entries ? entries.length : 0;
103+
104+
this.clear();
105+
106+
while (++index < length) {
107+
let entry = entries[index];
108+
this.set(entry[0], entry[1]);
109+
}
110+
}
111+
112+
clear() {
113+
this.__data__ = {
114+
'hash': new Hash(),
115+
// TODO: use native Map if present
116+
'map': new ListCache(),
117+
'string': new Hash()
118+
};
119+
}
120+
121+
delete(key) {
122+
return getMapData(this, key)['delete'](key);
123+
}
124+
125+
get(key) {
126+
return getMapData(this, key).get(key);
127+
}
128+
129+
has(key) {
130+
return getMapData(this, key).has(key);
131+
}
132+
133+
set(key, value) {
134+
getMapData(this, key).set(key, value);
135+
}
136+
137+
forEach(callback) {
138+
this.__data__.hash.forEach(callback);
139+
this.__data__.map.forEach(callback);
140+
this.__data__.string.forEach(callback);
141+
}
142+
}
143+
144+
export class ListCache {
145+
constructor(entries) {
146+
let index = -1;
147+
let length = entries ? entries.length : 0;
148+
149+
this.clear();
150+
151+
while (++index < length) {
152+
let entry = entries[index];
153+
this.set(entry[0], entry[1]);
154+
}
33155
}
34156

35157
clear() {
@@ -44,7 +166,7 @@ export default class ListCache {
44166
return false;
45167
}
46168
var lastIndex = data.length - 1;
47-
if (index == lastIndex) { // jshint ignore:line
169+
if (index == lastIndex) {
48170
data.pop();
49171
} else {
50172
data.splice(index, 1);
@@ -73,14 +195,8 @@ export default class ListCache {
73195
} else {
74196
data[index][1] = value;
75197
}
76-
77-
return this;
78198
}
79199

80-
/*
81-
This is not included in the lodash ListCache/Stack interface
82-
but is required to support all the operations of Meta.
83-
*/
84200
forEach(callback) {
85201
let index = -1;
86202

@@ -89,3 +205,41 @@ export default class ListCache {
89205
}
90206
}
91207
}
208+
209+
export default class Stack {
210+
constructor() {
211+
this.__data__ = new ListCache();
212+
}
213+
214+
clear() {
215+
this.__data__ = new ListCache();
216+
}
217+
218+
delete(key) {
219+
return this.__data__.delete(key);
220+
}
221+
222+
get(key) {
223+
return this.__data__.get(key);
224+
}
225+
226+
has(key) {
227+
return this.__data__.has(key);
228+
}
229+
230+
set(key, value) {
231+
let cache = this.__data__;
232+
if (cache instanceof ListCache && cache.__data__.length == LARGE_ARRAY_SIZE) {
233+
cache = this.__data__ = new MapCache(cache.__data__);
234+
}
235+
cache.set(key, value);
236+
}
237+
238+
/*
239+
This is not included in the lodash Stack interface
240+
but is required to support all the operations of Meta.
241+
*/
242+
forEach(callback) {
243+
this.__data__.forEach(callback);
244+
}
245+
}

0 commit comments

Comments
 (0)