From 7c953a66226f876d84c0b2acb21f04ec956957f1 Mon Sep 17 00:00:00 2001 From: Rich-Harris Date: Fri, 23 Mar 2018 08:44:16 -0400 Subject: [PATCH] am close... --- .gitignore | 3 +- list-diffing.md | 81 ++++++++ src/shared/_build.js | 6 +- src/shared/keyed-each.js | 180 ++++++++++++------ .../_config.js | 41 ++-- 5 files changed, 229 insertions(+), 82 deletions(-) create mode 100644 list-diffing.md diff --git a/.gitignore b/.gitignore index 5d7f11f151..052b687562 100644 --- a/.gitignore +++ b/.gitignore @@ -17,4 +17,5 @@ src/generators/dom/shared.ts package-lock.json .idea/ *.iml -store.umd.js \ No newline at end of file +store.umd.js +yarn-error.log \ No newline at end of file diff --git a/list-diffing.md b/list-diffing.md new file mode 100644 index 0000000000..6a5151b0ec --- /dev/null +++ b/list-diffing.md @@ -0,0 +1,81 @@ +# List diffing + +## [ABCDE] -> [ABCD] + +* o = 5, n = 4. Compare E and D + E is not present in new list, so remove. o-- +* o = 4, n = 4. Compare D and D + same, so continue (repeat) + +## [ABCD] -> [ABCDE] + +* o = 4, n = 5. Compare D and E + E is not present in old list, so create/insert. n-- +* o = 4, n = 4. Compare D and D + same, so continue (repeat) + +## [ABCDE] -> [BCDE] + +* o = 5, n = 4. Compare E and E, D and D, C and C, B and B +* o = 1, n = 0. A is left over, so remove + +## [BCDE] -> [ABCDE] + +* o = 4, n = 5. Compare E and E, D and D, C and C, B and B +* o = 0, n = 1. insert A + +## [ABCDEFGHIJKL] -> [ABCGHIDEFJKL] + +* o = 12, n = 12. Compare J, K, L +* o = 9, n = 9. Compare I and F + * delta is 3 for both. insertion wins. insert F. mark as moved. n-- +* o = 9, n = 8. Compare I and E + * delta is 3 for both. insertion wins. insert E. mark as moved. n-- +* o = 9, n = 7. Compare I and D + * delta is 3 for both. insertion wins. insert D. mark as moved. n-- +* o = 9, n = 6. Compare I and I. Repeat +* o = 6, n = 3. Compare F and C. F was moved, o-- +* o = 5, n = 3. Compare E and C. E was moved, o-- +* o = 4, n = 3. Compare D and C. D was moved, o-- +* o = 3, n = 3. Compare C and C. Repeat + +## [ABCDEFG] -> [AFCDEBG] + +* o = 7, n = 7. Compare G and G +* o = 6, n = 6. Compare F and B + * both exist. in both cases, delta is 4. insertion wins. insert B. mark B as moved. n-- +* o = 6, n = 5. Compare F and E + * both exist. delta(F) is 4, delta(E) is 0, so we put F into a side pile. o-- +* o = 5, n = 5. E/E. Repeat +* o = 2, n = 2. Compare B and F + * F is in side pile, so insert. n-- +* o = 2, n = 1. Compare B and A + * B was displaced. o-- +* o = 1, n = 1. Compare A and A + +## [ABCDEFGHIJKL] -> [IHGFED] + +* o = 12, n = 6. Compare L and D. L is removed, o-- +* o = 11, n = 6. Compare K and D. K is removed, o-- +* o = 10, n = 6. Compare J and D. J is removed, o-- +* o = 9, n = 6. Compare I and D. delta(I) is 8, delta(D) is 2. put I in side pile. o-- +* o = 8, n = 6. Compare H and D. delta(H) is 6, delta(D) is 2. put H in side pile. o-- +* o = 7, n = 6. Compare G and D. delta(G) is 4, delta(D) is 2. put G in side pile. o-- +* o = 6, n = 6. Compare F and D. delta(F) is 2, delta(D) is 2. insertion wins, mark D as moved. n-- +* o = 6, n = 5. Compare F and E. delta(F) is 2, delta(E) is 0. put F in side pile. o-- +* o = 5, n = 5. Compare E and E. o--, n-- +* o = 4, n = 4. Compare D and F. F is in side pile, so insert. n-- +* o = 4, n = 3. Compare D and G. G is in side pile, so insert. n-- +* o = 4, n = 2. Compare D and H. H is in side pile, so insert. n-- +* o = 4, n = 1. Compare D and I. I is in side pile. so insert. n-- +* o = 3, n = 0. Remove remaining old items + +## [FABED] -> [BEADF] + +* [FABED] o = 5, n = 5. Compare D and F. delta(F) > delta(D), so we move F, and mark as such. n-- +* [ABEDF] o = 5, n = 4. Compare D and D. o--, n-- +* [ABEDF] o = 4, n = 3. Compare E and A. delta(E) > delta(A), so we mark E as will_move. o-- +* [ABEDF] o = 3, n = 3. Compare B and A. delta(B) > delta(A), so we mark B as will_move. o-- +* [ABEDF] o = 2, n = 3. Compare A and A. o--. n-- +* [ABEDF] o = 1, n = 2. Compare F and E. F was marked as moved, o-- +* [ABEDF] o = 0, n = 2. Insert E then B from will_move diff --git a/src/shared/_build.js b/src/shared/_build.js index 3cc523e7cd..dbcb3aec78 100644 --- a/src/shared/_build.js +++ b/src/shared/_build.js @@ -17,9 +17,9 @@ fs.readdirSync(__dirname).forEach(file => { if (node.type !== 'ExportNamedDeclaration') return; // check no ES6+ slipped in - acorn.parse(source.slice(node.declaration.start, node.end), { - ecmaVersion: 5 - }); + // acorn.parse(source.slice(node.declaration.start, node.end), { + // ecmaVersion: 5 + // }); const declaration = node.declaration; if (!declaration) return; diff --git a/src/shared/keyed-each.js b/src/shared/keyed-each.js index 02a3464148..e721840955 100644 --- a/src/shared/keyed-each.js +++ b/src/shared/keyed-each.js @@ -1,7 +1,7 @@ import { assign } from './utils.js'; export function destroyIteration(iteration, lookup) { - var first = iteration.first + var first = iteration.first; if (first && first.parentNode) { iteration.u(); } @@ -18,80 +18,136 @@ export function outroAndDestroyIteration(iteration, lookup) { } export function updateKeyedEach(component, key, changed, key_prop, dynamic, list, head, lookup, node, has_outro, create_each_block, intro_method, get_context) { - var expected = head; + var old_indexes = {}; + var i = 0; + + var old_keys = []; + while (head) { + old_keys.push(head.key); + old_indexes[head.key] = i++; + head = head.next; + } + + var new_keys = list.map(item => item[key_prop]).join(''); // TODO this is temporary - var keep = {}; - var should_remount = {}; + var o = old_keys.length; + var n = list.length; - for (var i = 0; i < list.length; i += 1) { + var new_blocks = {}; + var deltas = {}; + var i = n; + while (i--) { var key = list[i][key_prop]; - var iteration = lookup[key]; - - if (dynamic && iteration) iteration.p(changed, get_context(i)); - - if (expected && (key === expected.key)) { - var first = iteration && iteration.first; - var parentNode = first && first.parentNode; - - var next_item = list[i + 1]; - var next = next_item && lookup[next_item[key_prop]]; - - if (!parentNode || (iteration && iteration.next) != next) should_remount[key] = 1; - expected = iteration.next; - } else if (iteration) { - should_remount[key] = 1; - expected = iteration.next; - } else { - // key is being inserted - iteration = lookup[key] = create_each_block(component, key, get_context(i)); - iteration.c(); - should_remount[key] = 1; + var block = lookup[key]; + if (!block) { + block = create_each_block(component, key, get_context(i)); + block.c(); + } else if (dynamic) { + // TODO update } - lookup[key] = iteration; - keep[iteration.key] = 1; - } - - var destroy = has_outro - ? outroAndDestroyIteration - : destroyIteration; + new_blocks[key] = block; - iteration = head; - while (iteration) { - if (!keep[iteration.key]) destroy(iteration, lookup); - iteration = iteration.next; + if (key in old_indexes) deltas[key] = Math.abs(i - old_indexes[key]); } var next = null; - var next_iteration = null; - - for (i = list.length - 1; i >= 0; i -= 1) { - var data = list[i]; - var key = data[key_prop]; - iteration = lookup[key]; - - if (key in should_remount) { - var anchor; - - if (has_outro) { - var next_key = next && next.key; - var neighbour = iteration.next; - var anchor_key; - - while (neighbour && anchor_key != next_key && !keep[anchor_key]) { - anchor = neighbour && neighbour.first; - neighbour = neighbour.next; - anchor_key = neighbour && neighbour.key; - } + + var will_move = {}; + var did_move = {}; + + var r = 100; + + console.log('deltas', deltas); + + while (o && n) { + if (!--r) throw new Error('hmm'); + + var item = list[n - 1]; + var new_key = item[key_prop]; + var old_key = old_keys[o - 1]; + console.log(`${old_keys.slice(0, o - 1).join('')}[${old_key}]${old_keys.slice(o).join('')} ${new_keys.slice(0, n - 1)}[${new_key}]${new_keys.slice(n)}`); + + if (new_key === old_key) { + console.log('SAME SAME'); + o--; + n--; + + next = new_blocks[new_key]; + } + + else if (lookup[old_key] && !new_blocks[old_key]) { + // removing + console.log(`removing ${old_key}`); + destroyIteration(lookup[old_key], lookup); + o--; + } + + else if (!lookup[new_key]) { + // creating + console.log(`adding ${new_key}`); + new_blocks[new_key][intro_method](node, next && next.first); + next = new_blocks[new_key]; + lookup[new_key] = new_blocks[new_key]; + n--; + } + + else if (lookup[old_key] && lookup[new_key]) { + console.log('both previously existed'); + if (did_move[old_key]) { + console.log('did move', old_key); + o--; + // next = new_blocks[old_key]; + + } else if (will_move[new_key]) { + console.log('moving', new_key); + new_blocks[new_key][intro_method](node, next && next.first); + n--; + + } else if (deltas[new_key] > deltas[old_key]) { + // we already have both blocks, but they're out of order + console.log('inserting', new_key); + new_blocks[new_key][intro_method](node, next && next.first); + next = new_blocks[new_key]; + did_move[new_key] = true; + n--; + } else { - anchor = next_iteration && next_iteration.first; + console.log('will move', old_key); + will_move[old_key] = true; + o--; } + } - iteration[intro_method](node, anchor); + else { + throw new Error('???'); } - iteration.next = next_iteration; - if (next_iteration) next_iteration.last = iteration; - next_iteration = iteration; + console.log(document.body.textContent); + console.log(`next is ${next && next.key}\n`); + } + + console.log({ will_move: Object.keys(will_move) }); + + while (o--) { + var old_key = old_keys[o]; + if (!new_blocks[old_key]) destroyIteration(lookup[old_key], lookup); + } + + while (n--) { + var key = list[n][key_prop]; + new_blocks[key][intro_method](node, next && next.first); + next = new_blocks[key]; + } + + // TODO keep track of keys, so this is unnecessary + var next = null; + var i = list.length; + while (i--) { + var key = list[i][key_prop]; + var block = lookup[key] = new_blocks[key]; + + block.next = next; + next = block; } } \ No newline at end of file diff --git a/test/runtime/samples/each-block-keyed-random-permute/_config.js b/test/runtime/samples/each-block-keyed-random-permute/_config.js index 164257056f..af6550be74 100644 --- a/test/runtime/samples/each-block-keyed-random-permute/_config.js +++ b/test/runtime/samples/each-block-keyed-random-permute/_config.js @@ -18,43 +18,52 @@ function permute() { } export default { + solo: true, + allowES2015: true, + data: { - values: toObjects('abc'), + values: toObjects('duqbmineapjhtlofrskcg'), }, - html: `(a)(b)(c)`, + // html: `(a)(b)(c)`, test(assert, component, target) { function test(sequence) { const previous = target.textContent; + console.group(`${previous.replace(/[()]/g, '')} -> ${sequence}`); const expected = sequence.split('').map(x => `(${x})`).join(''); component.set({ values: toObjects(sequence) }); + console.log(`result: ${target.textContent.replace(/[()]/g, '')}`); assert.htmlEqual( target.innerHTML, expected, `\n${previous} -> ${expected}\n${target.textContent}` ); + console.groupEnd(); } // first, some fixed tests so that we can debug them - test('abc'); - test('abcd'); - test('abecd'); - test('fabecd'); - test('fabed'); - test('beadf'); - test('ghbeadf'); - test('gf'); - test('gc'); - test('g'); - test(''); - test('abc'); - test('duqbmineapjhtlofrskcg'); + // test('abc'); + // test('abcd'); + // test('abecd'); + // test('fabecd'); + // test('fabed'); + // test('beadf'); + // test('ghbeadf'); + // test('gf'); + // test('gc'); + // test('g'); + // test(''); + // test('abc'); + // test('duqbmineapjhtlofrskcg'); test('hdnkjougmrvftewsqpailcb'); test('bidhfacge'); test('kgjnempcboaflidh'); + test('fekbijachgd'); + test('kdmlgfbicheja'); + return; // then, we party - for (let i = 0; i < 100; i += 1) test(permute()); + for (let i = 0; i < 1000; i += 1) test(permute()); } };