cleaner separation between EachState and EachItem - precursor to efficient relinking

pull/12215/head
Rich Harris 4 days ago
parent 319300e340
commit 9f76267774

@ -56,11 +56,12 @@ export function index(_, i) {
/** /**
* Pause multiple effects simultaneously, and coordinate their * Pause multiple effects simultaneously, and coordinate their
* subsequent destruction. Used in each blocks * subsequent destruction. Used in each blocks
* @param {import('#client').EachState} state
* @param {import('#client').EachItem[]} items * @param {import('#client').EachItem[]} items
* @param {null | Node} controlled_anchor * @param {null | Node} controlled_anchor
* @param {Map<any, import("#client").EachItem>} items_map * @param {Map<any, import("#client").EachItem>} items_map
*/ */
function pause_effects(items, controlled_anchor, items_map) { function pause_effects(state, items, controlled_anchor, items_map) {
/** @type {import('#client').TransitionManager[]} */ /** @type {import('#client').TransitionManager[]} */
var transitions = []; var transitions = [];
var length = items.length; var length = items.length;
@ -79,7 +80,7 @@ function pause_effects(items, controlled_anchor, items_map) {
clear_text_content(parent_node); clear_text_content(parent_node);
parent_node.append(/** @type {Element} */ (controlled_anchor)); parent_node.append(/** @type {Element} */ (controlled_anchor));
items_map.clear(); items_map.clear();
link(items[0].prev, items[length - 1].next); link(state, items[0].prev, items[length - 1].next);
} }
run_out_transitions(transitions, () => { run_out_transitions(transitions, () => {
@ -87,7 +88,7 @@ function pause_effects(items, controlled_anchor, items_map) {
var item = items[i]; var item = items[i];
if (!is_controlled) { if (!is_controlled) {
items_map.delete(item.k); items_map.delete(item.k);
link(item.prev, item.next); link(state, item.prev, item.next);
} }
destroy_effect(item.e, !is_controlled); destroy_effect(item.e, !is_controlled);
} }
@ -106,7 +107,7 @@ function pause_effects(items, controlled_anchor, items_map) {
*/ */
export function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn = null) { export function each(anchor, flags, get_collection, get_key, render_fn, fallback_fn = null) {
/** @type {import('#client').EachState} */ /** @type {import('#client').EachState} */
var state = { flags, items: new Map(), next: null }; var state = { flags, items: new Map(), first: null };
var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0; var is_controlled = (flags & EACH_IS_CONTROLLED) !== 0;
@ -165,8 +166,8 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback
/** @type {Node} */ /** @type {Node} */
var child_anchor = hydrate_start; var child_anchor = hydrate_start;
/** @type {import('#client').EachItem | import('#client').EachState} */ /** @type {import('#client').EachItem | null} */
var prev = state; var prev = null;
/** @type {import('#client').EachItem} */ /** @type {import('#client').EachItem} */
var item; var item;
@ -186,7 +187,7 @@ export function each(anchor, flags, get_collection, get_key, render_fn, fallback
child_anchor = hydrate_anchor(child_anchor); child_anchor = hydrate_anchor(child_anchor);
var value = array[i]; var value = array[i];
var key = get_key(value, i); var key = get_key(value, i);
item = create_item(child_anchor, prev, null, value, key, i, render_fn, flags); item = create_item(child_anchor, state, prev, null, value, key, i, render_fn, flags);
state.items.set(key, item); state.items.set(key, item);
child_anchor = /** @type {Comment} */ (child_anchor.nextSibling); child_anchor = /** @type {Comment} */ (child_anchor.nextSibling);
@ -244,14 +245,14 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
var length = array.length; var length = array.length;
var items = state.items; var items = state.items;
var first = state.next; var first = state.first;
var current = first; var current = first;
/** @type {Set<import('#client').EachItem>} */ /** @type {Set<import('#client').EachItem>} */
var seen = new Set(); var seen = new Set();
/** @type {import('#client').EachState | import('#client').EachItem} */ /** @type {import('#client').EachItem | null} */
var prev = state; var prev = null;
/** @type {Set<import('#client').EachItem>} */ /** @type {Set<import('#client').EachItem>} */
var to_animate = new Set(); var to_animate = new Set();
@ -295,7 +296,17 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
if (item === undefined) { if (item === undefined) {
var child_anchor = current ? get_first_node(current.e) : anchor; var child_anchor = current ? get_first_node(current.e) : anchor;
prev = create_item(child_anchor, prev, prev.next, value, key, i, render_fn, flags); prev = create_item(
child_anchor,
state,
prev,
prev === null ? state.first : prev.next,
value,
key,
i,
render_fn,
flags
);
items.set(key, prev); items.set(key, prev);
@ -338,9 +349,9 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
seen.delete(stashed[j]); seen.delete(stashed[j]);
} }
link(a.prev, b.next); link(state, a.prev, b.next);
link(prev, a); link(state, prev, a);
link(b, start); link(state, b, start);
current = start; current = start;
prev = b; prev = b;
@ -353,9 +364,9 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
seen.delete(item); seen.delete(item);
move(item, current, anchor); move(item, current, anchor);
link(item.prev, item.next); link(state, item.prev, item.next);
link(item, prev.next); link(state, item, prev === null ? state.first : prev.next);
link(prev, item); link(state, prev, item);
prev = item; prev = item;
} }
@ -405,7 +416,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
} }
} }
pause_effects(to_destroy, controlled_anchor, items); pause_effects(state, to_destroy, controlled_anchor, items);
} }
if (is_animated) { if (is_animated) {
@ -419,7 +430,7 @@ function reconcile(array, state, anchor, render_fn, flags, get_key) {
// TODO this is inefficient, this should happen during linking, but that's currently tricky // TODO this is inefficient, this should happen during linking, but that's currently tricky
/** @type {import('#client').Effect | null} */ /** @type {import('#client').Effect | null} */
var effect = null; var effect = null;
var next = state.next; var next = state.first;
/** @type {import('#client').Effect} */ (current_effect).first = next && next.e; /** @type {import('#client').Effect} */ (current_effect).first = next && next.e;
@ -463,7 +474,8 @@ function update_item(item, value, index, type) {
/** /**
* @template V * @template V
* @param {Node} anchor * @param {Node} anchor
* @param {import('#client').EachItem | import('#client').EachState} prev * @param {import('#client').EachState} state
* @param {import('#client').EachItem | null} prev
* @param {import('#client').EachItem | null} next * @param {import('#client').EachItem | null} next
* @param {V} value * @param {V} value
* @param {unknown} key * @param {unknown} key
@ -472,7 +484,7 @@ function update_item(item, value, index, type) {
* @param {number} flags * @param {number} flags
* @returns {import('#client').EachItem} * @returns {import('#client').EachItem}
*/ */
function create_item(anchor, prev, next, value, key, index, render_fn, flags) { function create_item(anchor, state, prev, next, value, key, index, render_fn, flags) {
var previous_each_item = current_each_item; var previous_each_item = current_each_item;
try { try {
@ -494,8 +506,15 @@ function create_item(anchor, prev, next, value, key, index, render_fn, flags) {
next next
}; };
prev.next = item; if (prev === null) {
if (next !== null) next.prev = item; state.first = item;
} else {
prev.next = item;
}
if (next !== null) {
next.prev = item;
}
current_each_item = item; current_each_item = item;
item.e = branch(() => render_fn(anchor, v, i)); item.e = branch(() => render_fn(anchor, v, i));
@ -525,11 +544,18 @@ function move(item, next, anchor) {
} }
/** /**
* * @param {import('#client').EachState} state
* @param {import('#client').EachItem | import('#client').EachState} prev * @param {import('#client').EachItem | null} prev
* @param {import('#client').EachItem | null} next * @param {import('#client').EachItem | null} next
*/ */
function link(prev, next) { function link(state, prev, next) {
prev.next = next; if (prev === null) {
if (next !== null) next.prev = prev; state.first = next;
} else {
prev.next = next;
}
if (next !== null) {
next.prev = prev;
}
} }

@ -71,7 +71,7 @@ export type EachState = {
/** a key -> item lookup */ /** a key -> item lookup */
items: Map<any, EachItem>; items: Map<any, EachItem>;
/** head of the linked list of items */ /** head of the linked list of items */
next: EachItem | null; first: EachItem | null;
}; };
export type EachItem = { export type EachItem = {
@ -85,7 +85,7 @@ export type EachItem = {
i: number | Source<number>; i: number | Source<number>;
/** key */ /** key */
k: unknown; k: unknown;
prev: EachItem | EachState; prev: EachItem | null;
next: EachItem | null; next: EachItem | null;
}; };

Loading…
Cancel
Save