Skip to content

Commit

Permalink
Merge pull request #1782 from bitovi/todomvc-perf
Browse files Browse the repository at this point in the history
Live-list diffing
  • Loading branch information
matthewp committed Jul 18, 2015
2 parents 64b5425 + 0692421 commit 85bdc3a
Show file tree
Hide file tree
Showing 16 changed files with 1,242 additions and 8 deletions.
1 change: 1 addition & 0 deletions util/array/array_test.js
@@ -0,0 +1 @@
steal("can/util/array/each_test.js","can/util/array/diff_test.js");
67 changes: 67 additions & 0 deletions util/array/diff.js
@@ -0,0 +1,67 @@
steal(function(){

var slice = [].slice;
// a b c
// a b c d
// [[2,0, d]]
return function(oldList, newList){
var oldIndex = 0,
newIndex = 0,
oldLength = oldList.length,
newLength = newList.length,
patches = [];

while(oldIndex < oldLength && newIndex < newLength) {
var oldItem = oldList[oldIndex],
newItem = newList[newIndex];

if( oldItem === newItem ) {
oldIndex++;
newIndex++;
continue;
}
// look for single insert, does the next newList item equal the current oldList.
// 1 2 3
// 1 2 4 3
if( newIndex+1 < newLength && newList[newIndex+1] === oldItem) {
patches.push({index: newIndex, deleteCount: 0, insert: [ newList[newIndex] ]});
oldIndex++;
newIndex += 2;
continue;
}
// look for single removal, does the next item in the oldList equal the current newList item.
// 1 2 3
// 1 3
else if( oldIndex+1 < oldLength && oldList[oldIndex+1] === newItem ) {
patches.push({index: newIndex, deleteCount: 1, insert: []});
oldIndex += 2;
newIndex++;
continue;
}
// just clean up the rest and exit
// 1 2 3
// 1 2 5 6 7
else {
patches.push(
{index: newIndex,
deleteCount: oldLength-oldIndex,
insert: slice.call(newList, newIndex) } );
return patches;
}
}
if( (newIndex === newLength) && (oldIndex === oldLength) ) {
return patches;
}
// a b
// a b c d e
patches.push(
{index: newIndex,
deleteCount: oldLength-oldIndex,
insert: slice.call(newList, newIndex) } );

return patches;
};
});

// a b c
// a d e b c
74 changes: 74 additions & 0 deletions util/array/diff_test.js
@@ -0,0 +1,74 @@
steal('can/util/array/diff.js', 'can/test', 'steal-qunit', function (diff) {

QUnit.test("diff", function(){

var patches = diff([], [1,2,3]);
deepEqual(patches, [{
index: 0,
deleteCount: 0,
insert: [1,2,3]
}], "insert many at end");

patches = diff([1,2,3], [1,2,3]);
deepEqual(patches,[],"no changes");

patches = diff([1,2,3],[1,2,3,4]);
deepEqual(patches, [{
index: 3,
deleteCount: 0,
insert: [4]
}],"add one at the end");

patches = diff([1,2,3,4], [1,2,4]);

deepEqual(patches, [{
index: 2,
deleteCount: 1,
insert: []
}],"remove one in the middle");

patches = diff(["a","b","z","f","x"],["a","b","f","w","z"]);
deepEqual(patches, [{
index: 2,
insert: [],
deleteCount: 1
},{
index: 3,
deleteCount: 1,
insert: ["w","z"]
}]);

patches = diff(["a","b","b"],["c","a","b"]);
deepEqual(patches, [{
index: 0,
insert: ["c"],
deleteCount: 0
},{
index: 3,
deleteCount: 1,
insert: []
}]);

// a, b, c, d, e, f, g
// a, c, d, e, f, g
// a, c, e, f, g
// a, c, e, g
patches = diff(["a","b","c","d","e","f","g"],["a","c","e","g"]);
deepEqual(patches, [{
index: 1,
insert: [],
deleteCount: 1
},{
index: 2,
deleteCount: 1,
insert: []
},
{
index: 3,
deleteCount: 1,
insert: []
}]);

});

});
2 changes: 1 addition & 1 deletion util/array/test.html
@@ -1,3 +1,3 @@
<title>can/util/array</title>
<script src="../../node_modules/steal/steal.js" main="can/util/array/each_test"></script>
<script src="../../node_modules/steal/steal.js" main="can/util/array/array_test"></script>
<div id="qunit-fixture"></div>
43 changes: 37 additions & 6 deletions view/live/live.js
@@ -1,4 +1,9 @@
steal('can/util', 'can/view/elements.js', 'can/view', 'can/view/node_lists', 'can/view/parser',function (can, elements, view, nodeLists, parser) {
steal('can/util',
'can/view/elements.js',
'can/view',
'can/view/node_lists',
'can/view/parser',
'can/util/array/diff.js', function (can, elements, view, nodeLists, parser, diff) {

elements = elements || can.view.elements;
nodeLists = nodeLists || can.view.NodeLists;
Expand Down Expand Up @@ -178,6 +183,7 @@ steal('can/util', 'can/view/elements.js', 'can/view', 'can/view/node_lists', 'ca
isTornDown = false,
// Called when items are added to the list.
add = function (ev, items, index) {

if (!afterPreviousEvents) {
return;
}
Expand Down Expand Up @@ -344,23 +350,48 @@ steal('can/util', 'can/view/elements.js', 'can/view', 'can/view/node_lists', 'ca
},
// Called when the list is replaced or setup.
updateList = function (ev, newList, oldList) {

if(isTornDown) {
return;
}
teardownList();


// make an empty list if the compute returns null or undefined
list = newList || [];


afterPreviousEvents = true;
if(newList && oldList) {
var patches = diff(oldList, newList);

if ( oldList.unbind ) {
oldList.unbind('add', add)
.unbind('remove', remove)
.unbind('move', move);
}
for(var i = 0, patchLen = patches.length; i < patchLen; i++) {
var patch = patches[i];
if(patch.deleteCount) {
remove({}, {
length: patch.deleteCount
}, patch.index, true);
}
if(patch.insert.length) {
add({}, patch.insert, patch.index);
}
}
} else {
teardownList();
add({}, list, 0);
}
afterPreviousEvents = false;
// list might be a plain array
if (list.bind) {
list.bind('add', add)
.bind('remove', remove)
.bind('move', move);
}
// temporarily allow add method.
afterPreviousEvents = true;
add({}, list, 0);
afterPreviousEvents = false;


can.batch.afterPreviousEvents(function(){
afterPreviousEvents = true;
Expand Down
3 changes: 3 additions & 0 deletions view/live/live_test.js
Expand Up @@ -133,10 +133,13 @@ steal("can/view/live", "can/observe", "can/test", "steal-qunit", function () {
equal(div.getElementsByTagName('label')
.length, 2, 'There are 2 labels');
div.getElementsByTagName('label')[0].myexpando = 'EXPANDO-ED';

map.attr('animals')
.push('turtle');

equal(div.getElementsByTagName('label')[0].myexpando, 'EXPANDO-ED', 'same expando');
equal(div.getElementsByTagName('span')[2].innerHTML, 'turtle', 'turtle added');

map.attr('animals', new can.List([
'sloth',
'bear',
Expand Down
141 changes: 141 additions & 0 deletions view/stache/benchmark/todomvc/base.css
@@ -0,0 +1,141 @@
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}

.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}

.learn a:hover {
text-decoration: underline;
color: #787e7e;
}

.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}

.learn h3 {
font-size: 24px;
}

.learn h4 {
font-size: 18px;
}

.learn h5 {
margin-bottom: 0;
font-size: 14px;
}

.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}

.learn li {
line-height: 20px;
}

.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}

#issue-count {
display: none;
}

.quote {
border: none;
margin: 20px 0 60px 0;
}

.quote p {
font-style: italic;
}

.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}

.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}

.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}

.quote footer img {
border-radius: 3px;
}

.quote footer a {
margin-left: 5px;
vertical-align: middle;
}

.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}

.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}

.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}

@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}

.learn-bar > .learn {
left: 8px;
}
}

0 comments on commit 85bdc3a

Please sign in to comment.