-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[PERF] optimise notifications when has-many array is changed #4583
Changes from 6 commits
0eeff74
4be1c97
fd60d3e
2625282
3674dca
9577e58
6b71d77
cdbaf87
27e2f11
b199170
8fe3411
993dd60
df30a5f
6e67c3d
e8c2c86
90dbc65
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,3 +23,4 @@ node_modules/ | |
bower_components/ | ||
.metadata_never_index | ||
npm-debug.log | ||
.vscodeignore | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -52,9 +52,16 @@ const { get, set } = Ember; | |
@uses Ember.MutableArray, Ember.Evented | ||
*/ | ||
export default Ember.Object.extend(Ember.MutableArray, Ember.Evented, { | ||
record: null, | ||
|
||
canonicalState: null, | ||
currentState: null, | ||
|
||
length: 0, | ||
|
||
init() { | ||
this._super(...arguments); | ||
|
||
this.currentState = Ember.A([]); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. because we need array observer and app might not have [] mapped to Ember.A? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this is a change on master btw - I've merged it into my branch because my branch started so long ago |
||
/** | ||
The loading state of this array | ||
|
||
|
@@ -141,31 +148,64 @@ export default Ember.Object.extend(Ember.MutableArray, Ember.Evented, { | |
return this.currentState[index].getRecord(); | ||
}, | ||
|
||
flushCanonical(isInitialized = true) { | ||
let toSet = this.canonicalState; | ||
flushCanonical() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
// It’s possible the parent side of the relationship may have been unloaded by this point | ||
if (!_objectIsAlive(this)) { | ||
return; | ||
} | ||
//TODO make this smarter, currently its plenty stupid | ||
let toSet = this.canonicalState.filter((internalModel) => !internalModel.isDeleted()); | ||
|
||
//a hack for not removing new records | ||
//TODO remove once we have proper diffing | ||
let newRecords = this.currentState.filter( | ||
const newRecords = this.currentState.filter( | ||
// only add new records which are not yet in the canonical state of this | ||
// relationship (a new record can be in the canonical state if it has | ||
// been 'acknowleged' to be in the relationship via a store.push) | ||
(internalModel) => internalModel.isNew() && toSet.indexOf(internalModel) === -1 | ||
); | ||
toSet = toSet.concat(newRecords); | ||
let oldLength = this.length; | ||
this.arrayContentWillChange(0, this.length, toSet.length); | ||
// It’s possible the parent side of the relationship may have been unloaded by this point | ||
if (_objectIsAlive(this)) { | ||
this.set('length', toSet.length); | ||
const oldLength = this.length; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what follows is relatively complicated, could it be extracted to its own "pure" function so it can be thoroughly unit tested? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
const newLength = toSet.length; | ||
|
||
const shortestLength = Math.min(oldLength, newLength); | ||
|
||
let firstChangeIndex = -1; // -1 signifies no changes | ||
// find the first change | ||
const currentArray = this.currentState; | ||
for (let i=0; i<shortestLength; i++) { | ||
// compare each item in the array | ||
if (currentArray[i] !== toSet[i]) { | ||
firstChangeIndex = i; | ||
break; | ||
} | ||
} | ||
this.currentState = toSet; | ||
this.arrayContentDidChange(0, oldLength, this.length); | ||
|
||
if (isInitialized) { | ||
//TODO Figure out to notify only on additions and maybe only if unloaded | ||
if (firstChangeIndex === -1) { | ||
// no change found in the matching part of the arrays | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this change is not needed, the scoped |
||
if (newLength !== oldLength) { | ||
firstChangeIndex = shortestLength; | ||
} | ||
} | ||
if (firstChangeIndex !== -1) { | ||
// we found a change, find the end of the change | ||
let unchangedEndBlockLength = 0; | ||
// walk back from the end of both arrays until we find a change | ||
for (let i=1; i<shortestLength; i++) { | ||
// compare each item in the array | ||
if (currentArray[oldLength-i] !== toSet[newLength-i]) { | ||
unchangedEndBlockLength = i-1; | ||
break; | ||
} | ||
} | ||
const added = newLength - unchangedEndBlockLength - firstChangeIndex; | ||
const removed = oldLength - unchangedEndBlockLength - firstChangeIndex; | ||
this.arrayContentWillChange(firstChangeIndex, removed, added); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if nothing changed, should be bail out here entirely? Seems like we don't want to emit any events in that case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. done |
||
set(this, 'length', toSet.length); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
this.currentState = toSet; | ||
this.arrayContentDidChange(firstChangeIndex, removed, added); | ||
this.relationship.notifyHasManyChanged(); | ||
} | ||
this.record.updateRecordArrays(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if there is no change found, why invoke There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought that's where the canonical was updated. I'll go and check again |
||
}, | ||
|
||
internalReplace(idx, amt, objects) { | ||
|
@@ -174,14 +214,15 @@ export default Ember.Object.extend(Ember.MutableArray, Ember.Evented, { | |
} | ||
this.arrayContentWillChange(idx, amt, objects.length); | ||
this.currentState.splice.apply(this.currentState, [idx, amt].concat(objects)); | ||
this.set('length', this.currentState.length); | ||
set(this, 'length', this.currentState.length); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this change seems unneeded |
||
this.arrayContentDidChange(idx, amt, objects.length); | ||
}, | ||
|
||
//TODO(Igor) optimize | ||
internalRemoveRecords(records) { | ||
let index; | ||
for (let i=0; i < records.length; i++) { | ||
let index = this.currentState.indexOf(records[i]); | ||
index = this.currentState.indexOf(records[i]); | ||
this.internalReplace(index, 1); | ||
} | ||
}, | ||
|
@@ -195,13 +236,12 @@ export default Ember.Object.extend(Ember.MutableArray, Ember.Evented, { | |
}, | ||
|
||
replace(idx, amt, objects) { | ||
let records; | ||
if (amt > 0) { | ||
records = this.currentState.slice(idx, idx+amt); | ||
this.get('relationship').removeRecords(records); | ||
const records = this.currentState.slice(idx, idx+amt); | ||
get(this, 'relationship').removeRecords(records); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
if (objects) { | ||
this.get('relationship').addRecords(objects.map(obj => obj._internalModel), idx); | ||
get(this, 'relationship').addRecords(objects.map(obj => obj._internalModel), idx); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
}, | ||
|
||
|
@@ -273,10 +313,10 @@ export default Ember.Object.extend(Ember.MutableArray, Ember.Evented, { | |
@return {DS.PromiseArray} promise | ||
*/ | ||
save() { | ||
let manyArray = this; | ||
let promiseLabel = 'DS: ManyArray#save ' + get(this, 'type'); | ||
let promise = Ember.RSVP.all(this.invoke("save"), promiseLabel). | ||
then(() => manyArray, null, 'DS: ManyArray#save return ManyArray'); | ||
const manyArray = this; | ||
const promiseLabel = `DS: ManyArray#save ${get(this, 'type')}`; | ||
const promise = Ember.RSVP.all(this.invoke("save"), promiseLabel) | ||
.then(() => manyArray, null, "DS: ManyArray#save return ManyArray"); | ||
|
||
return PromiseArray.create({ promise }); | ||
}, | ||
|
@@ -290,12 +330,11 @@ export default Ember.Object.extend(Ember.MutableArray, Ember.Evented, { | |
@return {DS.Model} record | ||
*/ | ||
createRecord(hash) { | ||
let store = get(this, 'store'); | ||
let type = get(this, 'type'); | ||
let record; | ||
|
||
const type = get(this, 'type'); | ||
assert(`You cannot add '${type.modelName}' records to this polymorphic relationship.`, !get(this, 'isPolymorphic')); | ||
record = store.createRecord(type.modelName, hash); | ||
|
||
const store = get(this, 'store'); | ||
const record = store.createRecord(type.modelName, hash); | ||
this.pushObject(record); | ||
|
||
return record; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unsure if this should be added here, typically editor specific things are not included.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove