Skip to content

Commit 74a6705

Browse files
authored
Don't abuse timers (#1108)
Previously, the object inspector would schedule an infinite timer that fires every 300ms to update the current object (it actually fires even where there isn't a current object to update). This is extremely inefficient, and it causes a lot of extra work in big apps, since the timer starts a runloop which results in a full top-down revalidation. The more appropriate thing to do is tap into the runloop callbacks, since as far as Ember is concerned, if there are no runloops, there are no observable changes.
1 parent 06b4824 commit 74a6705

File tree

3 files changed

+220
-171
lines changed

3 files changed

+220
-171
lines changed

ember_debug/object-inspector.js

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
import PortMixin from 'ember-debug/mixins/port-mixin';
2-
import { compareVersion } from 'ember-debug/utils/version';
2+
import bound from 'ember-debug/utils/bound-method';
33
import { isComputed, isDescriptor, getDescriptorFor } from 'ember-debug/utils/type-check';
4+
import { compareVersion } from 'ember-debug/utils/version';
45
import { typeOf } from './utils/type-check';
56

67
const Ember = window.Ember;
78
const {
89
Object: EmberObject, inspect: emberInspect, meta: emberMeta,
910
computed, get, set, guidFor, isNone,
10-
cacheFor, VERSION
11+
cacheFor, VERSION, run,
1112
} = Ember;
1213
const { oneWay } = computed;
14+
const { backburner, join } = run;
1315

1416
let glimmer;
1517
let metal;
@@ -266,21 +268,20 @@ export default EmberObject.extend(PortMixin, {
266268
});
267269
});
268270
}
269-
// workaround for tests, since calling any runloop inside runloop will prevent any `settled` to be called
270-
setTimeout(() => Ember.run.next(this, this.updateCurrentObject), 300);
271271
},
272272

273273
init() {
274274
this._super();
275275
this.set('sentObjects', {});
276-
Ember.run.next(this, this.updateCurrentObject);
276+
backburner.on('end', bound(this, this.updateCurrentObject));
277277
},
278278

279279
willDestroy() {
280280
this._super();
281281
for (let objectId in this.sentObjects) {
282282
this.releaseObject(objectId);
283283
}
284+
backburner.off('end', bound(this, this.updateCurrentObject));
284285
},
285286

286287
sentObjects: {},
@@ -391,7 +392,7 @@ export default EmberObject.extend(PortMixin, {
391392

392393
saveProperty(objectId, prop, val) {
393394
let object = this.sentObjects[objectId];
394-
set(object, prop, val);
395+
join(() => set(object, prop, val));
395396
},
396397

397398
sendToConsole(objectId, prop) {
@@ -489,7 +490,6 @@ export default EmberObject.extend(PortMixin, {
489490
if (meta._debugReferences === 0) {
490491
this.dropObject(guid);
491492
}
492-
493493
},
494494

495495
dropObject(objectId) {

ember_debug/utils/bound-method.js

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
const ENTRIES = new WeakMap();
2+
3+
function entriesFor(obj) {
4+
let entries = ENTRIES.get(obj);
5+
6+
if (entries === undefined) {
7+
entries = new WeakMap();
8+
ENTRIES.set(obj, entries);
9+
}
10+
11+
return entries;
12+
}
13+
14+
/**
15+
* Return `method.bind(obj)` or `obj[method].bind(obj)`. When called multiple
16+
* times, the same bound function will be returned.
17+
*
18+
* @param {Object} obj
19+
* @param {String|Symbol|Function} method
20+
* @return {Function}
21+
*/
22+
export default function bound(obj, method) {
23+
let func;
24+
25+
if (typeof method === 'function') {
26+
func = method;
27+
} else {
28+
func = obj[method];
29+
30+
if (typeof func !== 'function') {
31+
throw new TypeError(`${obj}[${method}] is not a function (was ${func})`);
32+
}
33+
}
34+
35+
let entries = entriesFor(obj);
36+
let bound = entries.get(func);
37+
38+
if (bound === undefined) {
39+
bound = func.bind(obj);
40+
entries.set(func, bound);
41+
}
42+
43+
return bound;
44+
}

0 commit comments

Comments
 (0)