|
265 | 265 | _targetFrameTime: {
|
266 | 266 | type: Number,
|
267 | 267 | computed: '__computeFrameTime(targetFramerate)'
|
268 |
| - }, |
269 |
| - |
270 |
| - /** |
271 |
| - * When `restamp` is true, template instances are never reused with |
272 |
| - * different items. Instances mapping to current items are moved |
273 |
| - * to their new location, new instances are created for any new |
274 |
| - * items, and instances for removed items are discarded. This mode is |
275 |
| - * generally more expensive than `restamp: false` as it results in |
276 |
| - * more instances to be created and discarded during updates and |
277 |
| - * disconnection/reconnection churn. By default, object identity is |
278 |
| - * used for mapping instances to items. Set `restampKey` to provide |
279 |
| - * key based identity. |
280 |
| - */ |
281 |
| - restamp: { |
282 |
| - type: Boolean |
283 |
| - }, |
284 |
| - |
285 |
| - /** |
286 |
| - * When `restamp: true` is used, `restampKey` can be used to provide |
287 |
| - * a path into the item object that serves as a unique key for the |
288 |
| - * item, for purposes of mapping instances to items. Instances for |
289 |
| - * items with the same key will be reused, while all others will be |
290 |
| - * either restamped or discarded. |
291 |
| - */ |
292 |
| - restampKey: { |
293 |
| - type: String |
294 | 268 | }
|
295 | 269 |
|
296 | 270 | }
|
|
305 | 279 | super();
|
306 | 280 | this.__instances = [];
|
307 | 281 | this.__limit = Infinity;
|
| 282 | + this.__pool = []; |
308 | 283 | this.__renderDebouncer = null;
|
309 | 284 | this.__itemsIdxToInstIdx = {};
|
310 | 285 | this.__chunkCount = null;
|
|
320 | 295 | disconnectedCallback() {
|
321 | 296 | super.disconnectedCallback();
|
322 | 297 | this.__isDetached = true;
|
323 |
| - let instances = this.__instances; |
324 |
| - for (let i=0; i<instances.length; i++) { |
325 |
| - this.__detachInstance(instances[i]); |
| 298 | + for (let i=0; i<this.__instances.length; i++) { |
| 299 | + this.__detachInstance(i); |
326 | 300 | }
|
327 | 301 | }
|
328 | 302 |
|
|
331 | 305 | // only perform attachment if the element was previously detached.
|
332 | 306 | if (this.__isDetached) {
|
333 | 307 | this.__isDetached = false;
|
334 |
| - let instances = this.__instances; |
335 |
| - for (let i=0; i<instances.length; i++) { |
336 |
| - this.__attachInstance(instances[i]); |
| 308 | + let parent = this.parentNode; |
| 309 | + for (let i=0; i<this.__instances.length; i++) { |
| 310 | + this.__attachInstance(i, parent); |
337 | 311 | }
|
338 | 312 | }
|
339 | 313 | }
|
|
523 | 497 | // No template found yet
|
524 | 498 | return;
|
525 | 499 | }
|
526 |
| - let items = this.items || []; |
527 |
| - let inst2items = new Array(items.length); |
528 |
| - for (let i=0; i<items.length; i++) { |
529 |
| - inst2items[i] = i; |
530 |
| - } |
531 |
| - // Apply user filter |
532 |
| - if (this.__filterFn) { |
533 |
| - inst2items = inst2items.filter((i, idx, array) => |
534 |
| - this.__filterFn(items[i], idx, array)); |
535 |
| - } |
536 |
| - // Apply user sort |
537 |
| - if (this.__sortFn) { |
538 |
| - inst2items.sort((a, b) => this.__sortFn(items[a], items[b])); |
539 |
| - } |
540 |
| - |
541 |
| - // items->inst map kept for item path forwarding |
542 |
| - const items2inst = this.__itemsIdxToInstIdx = {}; |
543 |
| - const instances = this.__instances; |
544 |
| - const limit = Math.max(Math.min(inst2items.length, this.__limit), 0); |
545 |
| - // Generate instances and assign items |
546 |
| - if (this.restamp) { |
547 |
| - this.__renderRestamp(items, instances, limit, inst2items, items2inst); |
548 |
| - } else { |
549 |
| - this.__renderRefresh(items, instances, limit, inst2items, items2inst); |
550 |
| - } |
551 |
| - // Remove any extra instances from previous state |
552 |
| - while (this.__instances.length > limit) { |
553 |
| - this.__detachAndRemoveInstanceAt(limit); |
554 |
| - } |
| 500 | + this.__applyFullRefresh(); |
| 501 | + // Reset the pool |
| 502 | + // TODO(kschaaf): Reuse pool across turns and nested templates |
| 503 | + // Now that objects/arrays are re-evaluated when set, we can safely |
| 504 | + // reuse pooled instances across turns, however we still need to decide |
| 505 | + // semantics regarding how long to hold, how many to hold, etc. |
| 506 | + this.__pool.length = 0; |
555 | 507 | // Set rendered item count
|
556 | 508 | this._setRenderedItemCount(this.__instances.length);
|
557 | 509 | // Notify users
|
|
563 | 515 | this.__tryRenderChunk();
|
564 | 516 | }
|
565 | 517 |
|
566 |
| - __renderRestamp(items, instances, limit, inst2items, items2inst) { |
567 |
| - let keyPath = this.restampKey; |
568 |
| - const instCache = new Map(); |
569 |
| - nextItem: for (let i=0; i<limit; i++) { |
570 |
| - let itemIdx = inst2items[i]; |
571 |
| - let item = items[itemIdx]; |
572 |
| - let key = keyPath && Polymer.Path.get(item, keyPath) || item; |
573 |
| - let inst, instItem; |
574 |
| - while ((inst = instances[i])) { |
575 |
| - instItem = inst[this.as]; |
576 |
| - let instKey = keyPath && Polymer.Path.get(instItem, keyPath) || instItem; |
577 |
| - if (key === instKey) { |
578 |
| - inst._setPendingProperty(this.as, item); |
579 |
| - inst._setPendingProperty(this.indexAs, i); |
580 |
| - inst._setPendingProperty(this.itemsIndexAs, itemIdx); |
581 |
| - inst._flushProperties(); |
582 |
| - items2inst[itemIdx] = i; |
583 |
| - continue nextItem; |
584 |
| - } |
585 |
| - let cache = instCache.get(instKey); |
586 |
| - if (cache) { |
587 |
| - cache.push(inst); |
588 |
| - } else { |
589 |
| - instCache.set(instKey, [inst]); |
590 |
| - } |
591 |
| - instances.splice(i, 1); |
592 |
| - } |
593 |
| - let cache = instCache.get(key); |
594 |
| - if (cache && (inst = cache.shift())) { |
595 |
| - this.__reuseInstance(inst, item, i, itemIdx); |
596 |
| - if (!cache.length) { |
597 |
| - instCache.delete(key); |
598 |
| - } |
599 |
| - } else { |
600 |
| - this.__insertInstance(item, i, itemIdx); |
601 |
| - } |
602 |
| - items2inst[itemIdx] = i; |
| 518 | + __applyFullRefresh() { |
| 519 | + let items = this.items || []; |
| 520 | + let isntIdxToItemsIdx = new Array(items.length); |
| 521 | + for (let i=0; i<items.length; i++) { |
| 522 | + isntIdxToItemsIdx[i] = i; |
603 | 523 | }
|
604 |
| - instCache.forEach(cache => cache.forEach(inst => this.__detachInstance(inst))); |
605 |
| - } |
606 |
| - |
607 |
| - __renderRefresh(items, instances, limit, inst2items, items2inst) { |
608 |
| - for (let i=0; i<limit; i++) { |
609 |
| - let inst = instances[i]; |
610 |
| - let itemIdx = inst2items[i]; |
| 524 | + // Apply user filter |
| 525 | + if (this.__filterFn) { |
| 526 | + isntIdxToItemsIdx = isntIdxToItemsIdx.filter((i, idx, array) => |
| 527 | + this.__filterFn(items[i], idx, array)); |
| 528 | + } |
| 529 | + // Apply user sort |
| 530 | + if (this.__sortFn) { |
| 531 | + isntIdxToItemsIdx.sort((a, b) => this.__sortFn(items[a], items[b])); |
| 532 | + } |
| 533 | + // items->inst map kept for item path forwarding |
| 534 | + const itemsIdxToInstIdx = this.__itemsIdxToInstIdx = {}; |
| 535 | + let instIdx = 0; |
| 536 | + // Generate instances and assign items |
| 537 | + const limit = Math.min(isntIdxToItemsIdx.length, this.__limit); |
| 538 | + for (; instIdx<limit; instIdx++) { |
| 539 | + let inst = this.__instances[instIdx]; |
| 540 | + let itemIdx = isntIdxToItemsIdx[instIdx]; |
611 | 541 | let item = items[itemIdx];
|
612 |
| - if (inst) { |
| 542 | + itemsIdxToInstIdx[itemIdx] = instIdx; |
| 543 | + if (inst && instIdx < this.__limit) { |
613 | 544 | inst._setPendingProperty(this.as, item);
|
614 |
| - inst._setPendingProperty(this.indexAs, i); |
| 545 | + inst._setPendingProperty(this.indexAs, instIdx); |
615 | 546 | inst._setPendingProperty(this.itemsIndexAs, itemIdx);
|
616 | 547 | inst._flushProperties();
|
617 | 548 | } else {
|
618 |
| - this.__insertInstance(item, i, itemIdx); |
| 549 | + this.__insertInstance(item, instIdx, itemIdx); |
619 | 550 | }
|
620 |
| - items2inst[itemIdx] = i; |
| 551 | + } |
| 552 | + // Remove any extra instances from previous state |
| 553 | + for (let i=this.__instances.length-1; i>=instIdx; i--) { |
| 554 | + this.__detachAndRemoveInstance(i); |
621 | 555 | }
|
622 | 556 | }
|
623 | 557 |
|
624 |
| - __detachInstance(inst) { |
| 558 | + __detachInstance(idx) { |
| 559 | + let inst = this.__instances[idx]; |
625 | 560 | for (let i=0; i<inst.children.length; i++) {
|
626 | 561 | let el = inst.children[i];
|
627 | 562 | inst.root.appendChild(el);
|
628 | 563 | }
|
| 564 | + return inst; |
629 | 565 | }
|
630 | 566 |
|
631 |
| - __reuseInstance(inst, item, idx, itemIdx) { |
632 |
| - let beforeInst = this.__instances[idx]; |
633 |
| - let beforeNode = beforeInst ? beforeInst.children[0] : this; |
634 |
| - for (let i=0; i<inst.children.length; i++) { |
635 |
| - this.parentNode.insertBefore(inst.children[i], beforeNode); |
636 |
| - } |
637 |
| - this.__instances.splice(idx, 0, inst); |
638 |
| - inst._setPendingProperty(this.as, item); |
639 |
| - inst._setPendingProperty(this.indexAs, idx); |
640 |
| - inst._setPendingProperty(this.itemsIndexAs, itemIdx); |
641 |
| - inst._flushProperties(); |
642 |
| - } |
643 |
| - |
644 |
| - __attachInstance(inst) { |
645 |
| - this.parentNode.insertBefore(inst.root, this); |
| 567 | + __attachInstance(idx, parent) { |
| 568 | + let inst = this.__instances[idx]; |
| 569 | + parent.insertBefore(inst.root, this); |
646 | 570 | }
|
647 | 571 |
|
648 |
| - __detachAndRemoveInstanceAt(idx) { |
649 |
| - this.__detachInstance(this.__instances[idx]); |
| 572 | + __detachAndRemoveInstance(idx) { |
| 573 | + let inst = this.__detachInstance(idx); |
| 574 | + if (inst) { |
| 575 | + this.__pool.push(inst); |
| 576 | + } |
650 | 577 | this.__instances.splice(idx, 1);
|
651 | 578 | }
|
652 | 579 |
|
|
659 | 586 | }
|
660 | 587 |
|
661 | 588 | __insertInstance(item, instIdx, itemIdx) {
|
662 |
| - let inst = this.__stampInstance(item, instIdx, itemIdx); |
663 |
| - let beforeRow = this.__instances[instIdx]; |
| 589 | + let inst = this.__pool.pop(); |
| 590 | + if (inst) { |
| 591 | + // TODO(kschaaf): If the pool is shared across turns, hostProps |
| 592 | + // need to be re-set to reused instances in addition to item |
| 593 | + inst._setPendingProperty(this.as, item); |
| 594 | + inst._setPendingProperty(this.indexAs, instIdx); |
| 595 | + inst._setPendingProperty(this.itemsIndexAs, itemIdx); |
| 596 | + inst._flushProperties(); |
| 597 | + } else { |
| 598 | + inst = this.__stampInstance(item, instIdx, itemIdx); |
| 599 | + } |
| 600 | + let beforeRow = this.__instances[instIdx + 1]; |
664 | 601 | let beforeNode = beforeRow ? beforeRow.children[0] : this;
|
665 | 602 | this.parentNode.insertBefore(inst.root, beforeNode);
|
666 |
| - this.__instances.splice(instIdx, 0, inst); |
| 603 | + this.__instances[instIdx] = inst; |
667 | 604 | return inst;
|
668 | 605 | }
|
669 | 606 |
|
|
0 commit comments