From 5e4e2ac498919782b22bb8d4eace91b7afbdb2c2 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 11:36:59 +0000 Subject: [PATCH 1/8] chore: opt for two signal data-structures to reduce memory usage --- .changeset/hungry-dots-fry.md | 5 ++ packages/svelte/src/internal/client/render.js | 6 +- .../svelte/src/internal/client/runtime.js | 82 ++++++++++++------- .../svelte/src/internal/client/transitions.js | 2 +- .../svelte/src/internal/client/types.d.ts | 47 +++++++---- 5 files changed, 93 insertions(+), 49 deletions(-) create mode 100644 .changeset/hungry-dots-fry.md diff --git a/.changeset/hungry-dots-fry.md b/.changeset/hungry-dots-fry.md new file mode 100644 index 000000000000..5c2328f6f92c --- /dev/null +++ b/.changeset/hungry-dots-fry.md @@ -0,0 +1,5 @@ +--- +'svelte': patch +--- + +chore: more signal perf tuning diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index c74105372480..1e46d79e787e 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -624,7 +624,7 @@ export function bind_playback_rate(media, get_value, update) { // Needs to happen after the element is inserted into the dom, else playback will be set back to 1 by the browser. // For hydration we could do it immediately but the additional code is not worth the lost microtask. - /** @type {import('./types.js').Signal | undefined} */ + /** @type {import('./types.js').ComputationSignal | undefined} */ let render; let destroyed = false; const effect = managed_effect(() => { @@ -2125,7 +2125,7 @@ export function destroy_each_item_block( if (!controlled && dom !== null) { remove(dom); } - destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect)); + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect)); } } @@ -3163,7 +3163,7 @@ export function mount(component, options) { if (hydration_fragment !== null) { remove(hydration_fragment); } - destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect)); + destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect)); } ]; } diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 7e317366329a..2207ac950727 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -46,7 +46,7 @@ let current_queued_tasks = []; let flush_count = 0; // Handle signal reactivity tree dependencies and consumer -/** @type {null | import('./types.js').Signal} */ +/** @type {null | import('./types.js').ComputationSignal} */ let current_consumer = null; /** @type {null | import('./types.js').EffectSignal} */ @@ -133,14 +133,31 @@ function default_equals(a, b) { return a === b; } +/** + * @template V + * @param {import('./types.js').SignalFlags} flags + * @param {V} value + * @returns {import('./types.js').SourceSignal} + */ +function create_source_signal(flags, value) { + return { + consumers: null, + // We can remove this if we get rid of beforeUpdate/afterUpdate + context: null, + equals: null, + flags, + value + }; +} + /** * @template V * @param {import('./types.js').SignalFlags} flags * @param {V} value * @param {import('./types.js').Block | null} block - * @returns {import('./types.js').Signal} + * @returns {import('./types.js').ComputationSignal} */ -function create_signal_object(flags, value, block) { +function create_computation_signal(flags, value, block) { return { block, consumers: null, @@ -156,8 +173,8 @@ function create_signal_object(flags, value, block) { } /** - * @param {import('./types.js').Signal} target_signal - * @param {import('./types.js').Signal} ref_signal + * @param {import('./types.js').ComputationSignal} target_signal + * @param {import('./types.js').ComputationSignal} ref_signal * @returns {void} */ function push_reference(target_signal, ref_signal) { @@ -180,7 +197,7 @@ function is_signal_dirty(signal) { return true; } if ((flags & MAYBE_DIRTY) !== 0) { - const dependencies = signal.dependencies; + const dependencies = /** @type {import('./types.js').ComputationSignal} **/ (signal).dependencies; if (dependencies !== null) { const length = dependencies.length; let i; @@ -195,7 +212,7 @@ function is_signal_dirty(signal) { // The flags can be marked as dirty from the above is_signal_dirty call. if ((dependency.flags & DIRTY) !== 0) { if ((dep_flags & DERIVED) !== 0) { - update_derived(dependency, true); + update_derived(/** @type {import('./types.js').ComputationSignal} **/ (dependency), true); // Might have been mutated from above get. if ((signal.flags & DIRTY) !== 0) { return true; @@ -212,7 +229,7 @@ function is_signal_dirty(signal) { /** * @template V - * @param {import('./types.js').Signal} signal + * @param {import('./types.js').ComputationSignal} signal * @returns {V} */ function execute_signal_fn(signal) { @@ -247,7 +264,7 @@ function execute_signal_fn(signal) { } else { res = /** @type {() => V} */ (init)(); } - let dependencies = signal.dependencies; + let dependencies = /** @type {import('./types.js').Signal[]} **/ (signal.dependencies); if (current_dependencies !== null) { let i; @@ -259,7 +276,7 @@ function execute_signal_fn(signal) { dependencies[current_dependencies_index + i] = current_dependencies[i]; } } else { - signal.dependencies = dependencies = current_dependencies; + signal.dependencies = /** @type {import('./types.js').Signal[]} **/ (dependencies = current_dependencies); } if (!current_skip_consumer) { @@ -291,7 +308,7 @@ function execute_signal_fn(signal) { /** * @template V - * @param {import('./types.js').Signal} signal + * @param {import('./types.js').ComputationSignal} signal * @param {number} start_index * @param {boolean} remove_unowned * @returns {void} @@ -316,7 +333,11 @@ function remove_consumer(signal, start_index, remove_unowned) { } } if (remove_unowned && consumers_length === 0 && (dependency.flags & UNOWNED) !== 0) { - remove_consumer(dependency, 0, true); + remove_consumer( + /** @type {import('./types.js').ComputationSignal} **/ (dependency), + 0, + true + ); } } } @@ -324,7 +345,7 @@ function remove_consumer(signal, start_index, remove_unowned) { /** * @template V - * @param {import('./types.js').Signal} signal + * @param {import('./types.js').ComputationSignal} signal * @returns {void} */ function destroy_references(signal) { @@ -575,7 +596,7 @@ export async function tick() { /** * @template V - * @param {import('./types.js').Signal} signal + * @param {import('./types.js').ComputationSignal} signal * @param {boolean} force_schedule * @returns {void} */ @@ -615,10 +636,11 @@ export function store_get(store, store_name, stores) { value: source(UNINITIALIZED), unsubscribe: EMPTY_FUNC }; - push_destroy_fn(entry.value, () => { - /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value = - /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value; - }); + // TODO: can we remove this code? it was refactored out when we split up source/comptued signals + // push_destroy_fn(entry.value, () => { + // /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value = + // /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value; + // }); stores[store_name] = entry; } @@ -676,7 +698,8 @@ export function unsubscribe_on_destroy(stores) { for (store_name in stores) { const ref = stores[store_name]; ref.unsubscribe(); - destroy_signal(ref.value); + // TODO: can we remove this code? it was refactored out when we split up source/comptued signals + // destroy_signal(ref.value); } }); } @@ -740,7 +763,7 @@ export function get(signal) { } if ((flags & DERIVED) !== 0 && is_signal_dirty(signal)) { - update_derived(signal, false); + update_derived(/** @type {import('./types.js').ComputationSignal} **/ (signal), false); } return signal.value; } @@ -845,7 +868,7 @@ export function mutate_store(store, expression, new_value) { } /** - * @param {import('./types.js').Signal} signal + * @param {import('./types.js').ComputationSignal} signal * @param {boolean} inert * @returns {void} */ @@ -969,12 +992,13 @@ export function set_signal_value(signal, value) { /** * @template V - * @param {import('./types.js').Signal} signal + * @param {import('./types.js').ComputationSignal} signal * @returns {void} */ export function destroy_signal(signal) { const teardown = /** @type {null | (() => void)} */ (signal.value); const destroy = signal.destroy; + const flags = signal.flags; destroy_references(signal); remove_consumer(signal, 0, true); signal.init = null; @@ -1005,14 +1029,14 @@ export function destroy_signal(signal) { * @template V * @param {() => V} init * @param {import('./types.js').EqualsFunctions} [equals] - * @returns {import('./types.js').Signal} + * @returns {import('./types.js').ComputationSignal} */ /*#__NO_SIDE_EFFECTS__*/ export function derived(init, equals) { const is_unowned = current_effect === null; const flags = is_unowned ? DERIVED | UNOWNED : DERIVED; - const signal = /** @type {import('./types.js').Signal} */ ( - create_signal_object(flags | CLEAN, UNINITIALIZED, current_block) + const signal = /** @type {import('./types.js').ComputationSignal} */ ( + create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block) ); signal.init = init; signal.context = current_component_context; @@ -1027,11 +1051,11 @@ export function derived(init, equals) { * @template V * @param {V} initial_value * @param {import('./types.js').EqualsFunctions} [equals] - * @returns {import('./types.js').Signal} + * @returns {import('./types.js').SourceSignal} */ /*#__NO_SIDE_EFFECTS__*/ export function source(initial_value, equals) { - const source = create_signal_object(SOURCE | CLEAN, initial_value, null); + const source = create_source_signal(SOURCE | CLEAN, initial_value); source.context = current_component_context; source.equals = get_equals_method(equals); return source; @@ -1079,7 +1103,7 @@ export function untrack(fn) { * @returns {import('./types.js').EffectSignal} */ function internal_create_effect(type, init, sync, block, schedule) { - const signal = create_signal_object(type | DIRTY, null, block); + const signal = create_computation_signal(type | DIRTY, null, block); signal.init = init; signal.context = current_component_context; if (schedule) { @@ -1210,7 +1234,7 @@ export function managed_render_effect(init, block = current_block, sync = true) /** * @template V - * @param {import('./types.js').Signal} signal + * @param {import('./types.js').ComputationSignal} signal * @param {() => void} destroy_fn * @returns {void} */ diff --git a/packages/svelte/src/internal/client/transitions.js b/packages/svelte/src/internal/client/transitions.js index b07db9335963..357e287d4e1e 100644 --- a/packages/svelte/src/internal/client/transitions.js +++ b/packages/svelte/src/internal/client/transitions.js @@ -253,7 +253,7 @@ function handle_raf(time) { * @param {HTMLElement} dom * @param {() => import('./types.js').TransitionPayload} init * @param {'in' | 'out' | 'both' | 'key'} direction - * @param {import('./types.js').Signal} effect + * @param {import('./types.js').EffectSignal} effect * @returns {import('./types.js').Transition} */ function create_transition(dom, init, direction, effect) { diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index 5bc4ce730b8f..fa9806d781ff 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -46,15 +46,28 @@ export type ComponentContext = { }; }; -export type Signal = { +export type SourceSignal = { + /** Signals that read from the current signal */ + consumers: null | ComputationSignal[]; + /** The associated component if this signal is an effect/computed */ + context: null | ComponentContext; + /** For value equality */ + equals: null | EqualsFunctions; + /** The types that the signal represent, as a bitwise value */ + flags: SignalFlags; + /** The latest value for this signal, doubles as the teardown for effects */ + value: V; +}; + +export type ComputationSignal = { /** The block associated with this effect/computed */ block: null | Block; /** Signals that read from the current signal */ - consumers: null | Signal[]; + consumers: null | ComputationSignal[]; /** The associated component if this signal is an effect/computed */ context: null | ComponentContext; /** Signals that this signal reads from */ - dependencies: null | Signal[]; + dependencies: null | Signal[]; /** Thing(s) that need destroying */ destroy: null | (() => void) | Array<() => void>; /** For value equality */ @@ -64,12 +77,14 @@ export type Signal = { /** The function that we invoke for effects and computeds */ init: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void)); /** Anything that a signal owns */ - references: null | Signal[]; + references: null | ComputationSignal[]; /** The latest value for this signal, doubles as the teardown for effects */ value: V; }; -export type EffectSignal = Signal void)>; +export type Signal = SourceSignal | ComputationSignal; + +export type EffectSignal = ComputationSignal void)>; export type MaybeSignal = T | Signal; @@ -92,7 +107,7 @@ export type BlockType = export type TemplateNode = Text | Element | Comment; export type Transition = { - effect: Signal; + effect: EffectSignal; payload: null | TransitionPayload; init: (from?: DOMRect) => TransitionPayload; finished: (fn: () => void) => void; @@ -106,7 +121,7 @@ export type Transition = { export type RootBlock = { dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; container: Node; intro: boolean; parent: null; @@ -117,7 +132,7 @@ export type RootBlock = { export type IfBlock = { current: boolean; dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; parent: Block; transition: null | ((transition: Transition) => void); type: typeof IF_BLOCK; @@ -125,7 +140,7 @@ export type IfBlock = { export type KeyBlock = { dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; parent: Block; transition: null | ((transition: Transition) => void); type: typeof KEY_BLOCK; @@ -133,7 +148,7 @@ export type KeyBlock = { export type HeadBlock = { dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; parent: Block; transition: null | ((transition: Transition) => void); type: typeof HEAD_BLOCK; @@ -141,7 +156,7 @@ export type HeadBlock = { export type DynamicElementBlock = { dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; parent: Block; transition: null | ((transition: Transition) => void); type: typeof DYNAMIC_ELEMENT_BLOCK; @@ -149,7 +164,7 @@ export type DynamicElementBlock = { export type DynamicComponentBlock = { dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; parent: Block; transition: null | ((transition: Transition) => void); type: typeof DYNAMIC_COMPONENT_BLOCK; @@ -157,7 +172,7 @@ export type DynamicComponentBlock = { export type AwaitBlock = { dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; parent: Block; pending: boolean; transition: null | ((transition: Transition) => void); @@ -169,7 +184,7 @@ export type EachBlock = { flags: number; dom: null | TemplateNode | Array; items: EachItemBlock[]; - effect: null | Signal; + effect: null | ComputationSignal; parent: Block; transition: null | ((transition: Transition) => void); transitions: Array; @@ -178,7 +193,7 @@ export type EachBlock = { export type EachItemBlock = { dom: null | TemplateNode | Array; - effect: null | Signal; + effect: null | ComputationSignal; item: any | Signal; index: number | Signal; key: unknown; @@ -191,7 +206,7 @@ export type EachItemBlock = { export type SnippetBlock = { dom: null | TemplateNode | Array; parent: Block; - effect: null | Signal; + effect: null | ComputationSignal; transition: null; type: typeof SNIPPET_BLOCK; }; From 16d84e411455c864098c5eaea8de9bb979243605 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 11:39:18 +0000 Subject: [PATCH 2/8] more tweaks --- packages/svelte/src/internal/client/render.js | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 1e46d79e787e..4f54e928b03c 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -26,7 +26,6 @@ import { EACH_IS_CONTROLLED, EACH_INDEX_REACTIVE, EACH_ITEM_REACTIVE, - EACH_IS_ANIMATED, PassiveDelegatedEvents, DelegatedEvents } from '../../constants.js'; @@ -2244,11 +2243,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re ? [] : Array.from(maybe_array); if (key_fn !== null) { - const length = array.length; - keys = Array(length); - for (let i = 0; i < length; i++) { - keys[i] = key_fn(array[i]); - } + keys = array.map(key_fn); } if (fallback_fn !== null) { if (array.length === 0) { From 3cad660792a46233064879d4f7f5b260e45c7339 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 12:34:11 +0000 Subject: [PATCH 3/8] more tweaks --- packages/svelte/src/internal/client/runtime.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index 2207ac950727..ed29687d0f3c 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -1,7 +1,6 @@ import { subscribe_to_store } from '../../store/utils.js'; import { EMPTY_FUNC } from '../common.js'; import { unwrap } from './render.js'; -import { map_delete, map_get, map_set } from './operations.js'; import { is_array } from './utils.js'; export const SOURCE = 1; @@ -197,22 +196,25 @@ function is_signal_dirty(signal) { return true; } if ((flags & MAYBE_DIRTY) !== 0) { - const dependencies = /** @type {import('./types.js').ComputationSignal} **/ (signal).dependencies; + const dependencies = /** @type {import('./types.js').ComputationSignal} **/ (signal) + .dependencies; if (dependencies !== null) { const length = dependencies.length; let i; for (i = 0; i < length; i++) { const dependency = dependencies[i]; - const dep_flags = dependency.flags; - if ((dep_flags & MAYBE_DIRTY) !== 0 && !is_signal_dirty(dependency)) { + if ((dependency.flags & MAYBE_DIRTY) !== 0 && !is_signal_dirty(dependency)) { set_signal_status(dependency, CLEAN); continue; } // The flags can be marked as dirty from the above is_signal_dirty call. if ((dependency.flags & DIRTY) !== 0) { - if ((dep_flags & DERIVED) !== 0) { - update_derived(/** @type {import('./types.js').ComputationSignal} **/ (dependency), true); + if ((dependency.flags & DERIVED) !== 0) { + update_derived( + /** @type {import('./types.js').ComputationSignal} **/ (dependency), + true + ); // Might have been mutated from above get. if ((signal.flags & DIRTY) !== 0) { return true; @@ -276,7 +278,9 @@ function execute_signal_fn(signal) { dependencies[current_dependencies_index + i] = current_dependencies[i]; } } else { - signal.dependencies = /** @type {import('./types.js').Signal[]} **/ (dependencies = current_dependencies); + signal.dependencies = /** @type {import('./types.js').Signal[]} **/ ( + dependencies = current_dependencies + ); } if (!current_skip_consumer) { From 0e9161f9accbf62c7e043ca9ece86579eb57b85b Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 12:57:29 +0000 Subject: [PATCH 4/8] use single character properties --- .../3-transform/client/visitors/global.js | 8 +- packages/svelte/src/internal/client/render.js | 2 +- .../svelte/src/internal/client/runtime.js | 229 +++++++++--------- .../svelte/src/internal/client/types.d.ts | 63 ++--- .../_expected/client/index.svelte.js | 6 +- 5 files changed, 154 insertions(+), 154 deletions(-) diff --git a/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js b/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js index 4c1d8af7b8d7..4b053e0566e2 100644 --- a/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js +++ b/packages/svelte/src/compiler/phases/3-transform/client/visitors/global.js @@ -14,21 +14,21 @@ export const global_visitors = { }, MemberExpression(node, { state, next }) { if (node.object.type === 'ThisExpression') { - // rewrite `this.#foo` as `this.#foo.value` inside a constructor + // rewrite `this.#foo` as `this.#foo.v` inside a constructor if (node.property.type === 'PrivateIdentifier') { const field = state.private_state.get(node.property.name); if (field) { - return state.in_constructor ? b.member(node, b.id('value')) : b.call('$.get', node); + return state.in_constructor ? b.member(node, b.id('v')) : b.call('$.get', node); } } - // rewrite `this.foo` as `this.#foo.value` inside a constructor + // rewrite `this.foo` as `this.#foo.v` inside a constructor if (node.property.type === 'Identifier' && !node.computed) { const field = state.public_state.get(node.property.name); if (field && state.in_constructor) { - return b.member(b.member(b.this, field.id), b.id('value')); + return b.member(b.member(b.this, field.id), b.id('v')); } } } diff --git a/packages/svelte/src/internal/client/render.js b/packages/svelte/src/internal/client/render.js index 4f54e928b03c..b860a4c3f661 100644 --- a/packages/svelte/src/internal/client/render.js +++ b/packages/svelte/src/internal/client/render.js @@ -2082,7 +2082,7 @@ export function update_each_item_block(block, item, index, type) { if (transitions !== null && (type & EACH_KEYED) !== 0) { let prev_index = block.index; if (index_is_reactive) { - prev_index = /** @type {import('./types.js').Signal} */ (prev_index).value; + prev_index = /** @type {import('./types.js').Signal} */ (prev_index).v; } const items = block.parent.items; if (prev_index !== index && /** @type {number} */ (index) < items.length) { diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index ed29687d0f3c..ae42d9c869ab 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -140,12 +140,12 @@ function default_equals(a, b) { */ function create_source_signal(flags, value) { return { - consumers: null, + c: null, // We can remove this if we get rid of beforeUpdate/afterUpdate - context: null, - equals: null, - flags, - value + x: null, + e: null, + f: flags, + v: value }; } @@ -158,16 +158,16 @@ function create_source_signal(flags, value) { */ function create_computation_signal(flags, value, block) { return { - block, - consumers: null, - context: null, - dependencies: null, - destroy: null, - equals: null, - flags, - init: null, - references: null, - value + b: block, + c: null, + x: null, + d: null, + y: null, + e: null, + f: flags, + i: null, + r: null, + v: value }; } @@ -177,9 +177,9 @@ function create_computation_signal(flags, value, block) { * @returns {void} */ function push_reference(target_signal, ref_signal) { - const references = target_signal.references; + const references = target_signal.r; if (references === null) { - target_signal.references = [ref_signal]; + target_signal.r = [ref_signal]; } else { references.push(ref_signal); } @@ -191,32 +191,31 @@ function push_reference(target_signal, ref_signal) { * @returns {boolean} */ function is_signal_dirty(signal) { - const flags = signal.flags; - if ((flags & DIRTY) !== 0 || signal.value === UNINITIALIZED) { + const flags = signal.f; + if ((flags & DIRTY) !== 0 || signal.v === UNINITIALIZED) { return true; } if ((flags & MAYBE_DIRTY) !== 0) { - const dependencies = /** @type {import('./types.js').ComputationSignal} **/ (signal) - .dependencies; + const dependencies = /** @type {import('./types.js').ComputationSignal} **/ (signal).d; if (dependencies !== null) { const length = dependencies.length; let i; for (i = 0; i < length; i++) { const dependency = dependencies[i]; - if ((dependency.flags & MAYBE_DIRTY) !== 0 && !is_signal_dirty(dependency)) { + if ((dependency.f & MAYBE_DIRTY) !== 0 && !is_signal_dirty(dependency)) { set_signal_status(dependency, CLEAN); continue; } // The flags can be marked as dirty from the above is_signal_dirty call. - if ((dependency.flags & DIRTY) !== 0) { - if ((dependency.flags & DERIVED) !== 0) { + if ((dependency.f & DIRTY) !== 0) { + if ((dependency.f & DERIVED) !== 0) { update_derived( /** @type {import('./types.js').ComputationSignal} **/ (dependency), true ); // Might have been mutated from above get. - if ((signal.flags & DIRTY) !== 0) { + if ((signal.f & DIRTY) !== 0) { return true; } } else { @@ -235,21 +234,21 @@ function is_signal_dirty(signal) { * @returns {V} */ function execute_signal_fn(signal) { - const init = signal.init; + const init = signal.i; const previous_dependencies = current_dependencies; const previous_dependencies_index = current_dependencies_index; const previous_consumer = current_consumer; const previous_block = current_block; const previous_component_context = current_component_context; const previous_skip_consumer = current_skip_consumer; - const is_render_effect = (signal.flags & RENDER_EFFECT) !== 0; + const is_render_effect = (signal.f & RENDER_EFFECT) !== 0; const previous_untracking = current_untracking; current_dependencies = /** @type {null | import('./types.js').Signal[]} */ (null); current_dependencies_index = 0; current_consumer = signal; - current_block = signal.block; - current_component_context = signal.context; - current_skip_consumer = current_effect === null && (signal.flags & UNOWNED) !== 0; + current_block = signal.b; + current_component_context = signal.x; + current_skip_consumer = current_effect === null && (signal.f & UNOWNED) !== 0; current_untracking = false; // Render effects are invoked when the UI is about to be updated - run beforeUpdate at that point @@ -261,12 +260,12 @@ function execute_signal_fn(signal) { let res; if (is_render_effect) { res = /** @type {(block: import('./types.js').Block) => V} */ (init)( - /** @type {import('./types.js').Block} */ (signal.block) + /** @type {import('./types.js').Block} */ (signal.b) ); } else { res = /** @type {() => V} */ (init)(); } - let dependencies = /** @type {import('./types.js').Signal[]} **/ (signal.dependencies); + let dependencies = /** @type {import('./types.js').Signal[]} **/ (signal.d); if (current_dependencies !== null) { let i; @@ -278,7 +277,7 @@ function execute_signal_fn(signal) { dependencies[current_dependencies_index + i] = current_dependencies[i]; } } else { - signal.dependencies = /** @type {import('./types.js').Signal[]} **/ ( + signal.d = /** @type {import('./types.js').Signal[]} **/ ( dependencies = current_dependencies ); } @@ -287,10 +286,10 @@ function execute_signal_fn(signal) { for (i = current_dependencies_index; i < dependencies.length; i++) { const dependency = dependencies[i]; - if (dependency.consumers === null) { - dependency.consumers = [signal]; + if (dependency.c === null) { + dependency.c = [signal]; } else { - dependency.consumers.push(signal); + dependency.c.push(signal); } } } @@ -318,17 +317,17 @@ function execute_signal_fn(signal) { * @returns {void} */ function remove_consumer(signal, start_index, remove_unowned) { - const dependencies = signal.dependencies; + const dependencies = signal.d; if (dependencies !== null) { let i; for (i = start_index; i < dependencies.length; i++) { const dependency = dependencies[i]; - const consumers = dependency.consumers; + const consumers = dependency.c; let consumers_length = 0; if (consumers !== null) { consumers_length = consumers.length - 1; if (consumers_length === 0) { - dependency.consumers = null; + dependency.c = null; } else { const index = consumers.indexOf(signal); // Swap with last element and then remove. @@ -336,7 +335,7 @@ function remove_consumer(signal, start_index, remove_unowned) { consumers.pop(); } } - if (remove_unowned && consumers_length === 0 && (dependency.flags & UNOWNED) !== 0) { + if (remove_unowned && consumers_length === 0 && (dependency.f & UNOWNED) !== 0) { remove_consumer( /** @type {import('./types.js').ComputationSignal} **/ (dependency), 0, @@ -353,17 +352,17 @@ function remove_consumer(signal, start_index, remove_unowned) { * @returns {void} */ function destroy_references(signal) { - const references = signal.references; - signal.references = null; + const references = signal.r; + signal.r = null; if (references !== null) { let i; for (i = 0; i < references.length; i++) { const reference = references[i]; - if ((reference.flags & IS_EFFECT) !== 0) { + if ((reference.f & IS_EFFECT) !== 0) { destroy_signal(reference); } else { remove_consumer(reference, 0, true); - reference.dependencies = null; + reference.d = null; } } } @@ -388,10 +387,10 @@ function report_error(block, error) { * @returns {void} */ export function execute_effect(signal) { - if ((signal.flags & DESTROYED) !== 0) { + if ((signal.f & DESTROYED) !== 0) { return; } - const teardown = signal.value; + const teardown = signal.v; const previous_effect = current_effect; current_effect = signal; @@ -402,10 +401,10 @@ export function execute_effect(signal) { } const possible_teardown = execute_signal_fn(signal); if (typeof possible_teardown === 'function') { - signal.value = possible_teardown; + signal.v = possible_teardown; } } catch (error) { - const block = signal.block; + const block = signal.b; if (block !== null) { report_error(block, error); } else { @@ -414,10 +413,10 @@ export function execute_effect(signal) { } finally { current_effect = previous_effect; } - const component_context = signal.context; + const component_context = signal.x; if ( is_runes(component_context) && // Don't rerun pre effects more than once to accomodate for "$: only runs once" behavior - (signal.flags & PRE_EFFECT) !== 0 && + (signal.f & PRE_EFFECT) !== 0 && current_queued_pre_and_render_effects.length > 0 ) { flush_local_pre_effects(component_context); @@ -441,7 +440,7 @@ function flush_queued_effects(effects) { let i; for (i = 0; i < length; i++) { const signal = effects[i]; - const flags = signal.flags; + const flags = signal.f; if ((flags & (DESTROYED | INERT)) === 0) { if (is_signal_dirty(signal)) { set_signal_status(signal, CLEAN); @@ -477,7 +476,7 @@ function process_microtask() { * @returns {void} */ export function schedule_effect(signal, sync) { - const flags = signal.flags; + const flags = signal.f; if (sync || (flags & SYNC_EFFECT) !== 0) { execute_effect(signal); set_signal_status(signal, CLEAN); @@ -524,7 +523,7 @@ export function flush_local_render_effects() { const effects = []; for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) { const effect = current_queued_pre_and_render_effects[i]; - if ((effect.flags & RENDER_EFFECT) !== 0 && effect.context === current_component_context) { + if ((effect.f & RENDER_EFFECT) !== 0 && effect.x === current_component_context) { effects.push(effect); current_queued_pre_and_render_effects.splice(i, 1); i--; @@ -541,7 +540,7 @@ export function flush_local_pre_effects(context) { const effects = []; for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) { const effect = current_queued_pre_and_render_effects[i]; - if ((effect.flags & PRE_EFFECT) !== 0 && effect.context === context) { + if ((effect.f & PRE_EFFECT) !== 0 && effect.x === context) { effects.push(effect); current_queued_pre_and_render_effects.splice(i, 1); i--; @@ -607,13 +606,13 @@ export async function tick() { function update_derived(signal, force_schedule) { const value = execute_signal_fn(signal); const status = - current_skip_consumer || (current_effect === null && (signal.flags & UNOWNED) !== 0) + current_skip_consumer || (current_effect === null && (signal.f & UNOWNED) !== 0) ? DIRTY : CLEAN; set_signal_status(signal, status); - const equals = /** @type {import('./types.js').EqualsFunctions} */ (signal.equals); - if (!equals(value, signal.value)) { - signal.value = value; + const equals = /** @type {import('./types.js').EqualsFunctions} */ (signal.e); + if (!equals(value, signal.v)) { + signal.v = value; mark_signal_consumers(signal, DIRTY, force_schedule); } } @@ -731,9 +730,9 @@ export function exposable(fn) { * @returns {V} */ export function get(signal) { - const flags = signal.flags; + const flags = signal.f; if ((flags & DESTROYED) !== 0) { - return signal.value; + return signal.v; } if (is_signal_exposed && current_should_capture_signal) { @@ -745,13 +744,9 @@ export function get(signal) { } // Register the dependency on the current consumer signal. - if ( - current_consumer !== null && - (current_consumer.flags & MANAGED) === 0 && - !current_untracking - ) { - const unowned = (current_consumer.flags & UNOWNED) !== 0; - const dependencies = current_consumer.dependencies; + if (current_consumer !== null && (current_consumer.f & MANAGED) === 0 && !current_untracking) { + const unowned = (current_consumer.f & UNOWNED) !== 0; + const dependencies = current_consumer.d; if ( current_dependencies === null && dependencies !== null && @@ -769,7 +764,7 @@ export function get(signal) { if ((flags & DERIVED) !== 0 && is_signal_dirty(signal)) { update_derived(/** @type {import('./types.js').ComputationSignal} **/ (signal), false); } - return signal.value; + return signal.v; } /** @@ -877,14 +872,14 @@ export function mutate_store(store, expression, new_value) { * @returns {void} */ export function mark_subtree_inert(signal, inert) { - const flags = signal.flags; + const flags = signal.f; if (((flags & INERT) === 0 && inert) || ((flags & INERT) !== 0 && !inert)) { - signal.flags ^= INERT; + signal.f ^= INERT; if (!inert && (flags & IS_EFFECT) !== 0 && (flags & CLEAN) === 0) { schedule_effect(/** @type {import('./types.js').EffectSignal} */ (signal), false); } } - const references = signal.references; + const references = signal.r; if (references !== null) { let i; for (i = 0; i < references.length; i++) { @@ -901,14 +896,14 @@ export function mark_subtree_inert(signal, inert) { * @returns {void} */ function mark_signal_consumers(signal, to_status, force_schedule) { - const runes = is_runes(signal.context); - const consumers = signal.consumers; + const runes = is_runes(signal.x); + const consumers = signal.c; if (consumers !== null) { const length = consumers.length; let i; for (i = 0; i < length; i++) { const consumer = consumers[i]; - const flags = consumer.flags; + const flags = consumer.f; const unowned = (flags & UNOWNED) !== 0; const dirty = (flags & DIRTY) !== 0; // We skip any effects that are already dirty (but not unowned). Additionally, we also @@ -922,7 +917,7 @@ function mark_signal_consumers(signal, to_status, force_schedule) { // are already dirty. Unowned signals might be dirty because they are not captured as part of an // effect. if ((flags & CLEAN) !== 0 || (dirty && unowned)) { - if ((consumer.flags & IS_EFFECT) !== 0) { + if ((consumer.f & IS_EFFECT) !== 0) { schedule_effect(/** @type {import('./types.js').EffectSignal} */ (consumer), false); } else { mark_signal_consumers(consumer, MAYBE_DIRTY, force_schedule); @@ -943,8 +938,8 @@ export function set_signal_value(signal, value) { !current_untracking && !ignore_mutation_validation && current_consumer !== null && - is_runes(signal.context) && - (current_consumer.flags & DERIVED) !== 0 + is_runes(signal.x) && + (current_consumer.f & DERIVED) !== 0 ) { throw new Error( "Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " + @@ -953,11 +948,11 @@ export function set_signal_value(signal, value) { ); } if ( - (signal.flags & SOURCE) !== 0 && - !(/** @type {import('./types.js').EqualsFunctions} */ (signal.equals)(value, signal.value)) + (signal.f & SOURCE) !== 0 && + !(/** @type {import('./types.js').EqualsFunctions} */ (signal.e)(value, signal.v)) ) { - const component_context = signal.context; - signal.value = value; + const component_context = signal.x; + signal.v = value; // If the current signal is running for the first time, it won't have any // consumers as we only allocate and assign the consumers after the signal // has fully executed. So in the case of ensuring it registers the consumer @@ -969,8 +964,8 @@ export function set_signal_value(signal, value) { if ( is_runes(component_context) && current_effect !== null && - current_effect.consumers === null && - (current_effect.flags & CLEAN) !== 0 && + current_effect.c === null && + (current_effect.f & CLEAN) !== 0 && current_dependencies !== null && current_dependencies.includes(signal) ) { @@ -1000,19 +995,19 @@ export function set_signal_value(signal, value) { * @returns {void} */ export function destroy_signal(signal) { - const teardown = /** @type {null | (() => void)} */ (signal.value); - const destroy = signal.destroy; - const flags = signal.flags; + const teardown = /** @type {null | (() => void)} */ (signal.v); + const destroy = signal.y; + const flags = signal.f; destroy_references(signal); remove_consumer(signal, 0, true); - signal.init = null; - signal.references = null; - signal.destroy = null; - signal.context = null; - signal.block = null; - signal.value = /** @type {V} */ (null); - signal.dependencies = null; - signal.consumers = null; + signal.i = null; + signal.r = null; + signal.y = null; + signal.x = null; + signal.b = null; + signal.v = /** @type {V} */ (null); + signal.d = null; + signal.c = null; set_signal_status(signal, DESTROYED); if (destroy !== null) { if (is_array(destroy)) { @@ -1024,7 +1019,7 @@ export function destroy_signal(signal) { destroy(); } } - if (teardown !== null && (signal.flags & IS_EFFECT) !== 0) { + if (teardown !== null && (flags & IS_EFFECT) !== 0) { teardown(); } } @@ -1042,9 +1037,9 @@ export function derived(init, equals) { const signal = /** @type {import('./types.js').ComputationSignal} */ ( create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block) ); - signal.init = init; - signal.context = current_component_context; - signal.equals = get_equals_method(equals); + signal.i = init; + signal.x = current_component_context; + signal.e = get_equals_method(equals); if (!is_unowned) { push_reference(/** @type {import('./types.js').EffectSignal} */ (current_effect), signal); } @@ -1060,8 +1055,8 @@ export function derived(init, equals) { /*#__NO_SIDE_EFFECTS__*/ export function source(initial_value, equals) { const source = create_source_signal(SOURCE | CLEAN, initial_value); - source.context = current_component_context; - source.equals = get_equals_method(equals); + source.x = current_component_context; + source.e = get_equals_method(equals); return source; } @@ -1108,8 +1103,8 @@ export function untrack(fn) { */ function internal_create_effect(type, init, sync, block, schedule) { const signal = create_computation_signal(type | DIRTY, null, block); - signal.init = init; - signal.context = current_component_context; + signal.i = init; + signal.x = current_component_context; if (schedule) { schedule_effect(signal, sync); } @@ -1128,7 +1123,7 @@ export function user_effect(init) { throw new Error('The Svelte $effect rune can only be used during component initialisation.'); } const apply_component_effect_heuristics = - current_effect.flags & RENDER_EFFECT && + current_effect.f & RENDER_EFFECT && current_component_context !== null && !current_component_context.mounted; const effect = internal_create_effect( @@ -1186,7 +1181,7 @@ export function pre_effect(init) { 'The Svelte $effect.pre rune can only be used during component initialisation.' ); } - const sync = current_effect !== null && (current_effect.flags & RENDER_EFFECT) !== 0; + const sync = current_effect !== null && (current_effect.f & RENDER_EFFECT) !== 0; return internal_create_effect( PRE_EFFECT, () => { @@ -1243,13 +1238,13 @@ export function managed_render_effect(init, block = current_block, sync = true) * @returns {void} */ export function push_destroy_fn(signal, destroy_fn) { - let destroy = signal.destroy; + let destroy = signal.y; if (destroy === null) { - signal.destroy = destroy_fn; + signal.y = destroy_fn; } else if (is_array(destroy)) { destroy.push(destroy_fn); } else { - signal.destroy = [destroy, destroy_fn]; + signal.y = [destroy, destroy_fn]; } } @@ -1260,16 +1255,16 @@ export function push_destroy_fn(signal, destroy_fn) { * @returns {void} */ export function set_signal_status(signal, status) { - const flags = signal.flags; + const flags = signal.f; if ((flags & status) === 0) { if ((flags & MAYBE_DIRTY) !== 0) { - signal.flags ^= MAYBE_DIRTY; + signal.f ^= MAYBE_DIRTY; } else if ((flags & CLEAN) !== 0) { - signal.flags ^= CLEAN; + signal.f ^= CLEAN; } else if ((flags & DIRTY) !== 0) { - signal.flags ^= DIRTY; + signal.f ^= DIRTY; } - signal.flags ^= status; + signal.f ^= status; } } @@ -1282,7 +1277,7 @@ export function is_signal(val) { return ( typeof val === 'object' && val !== null && - typeof (/** @type {import('./types.js').Signal} */ (val).flags) === 'number' + typeof (/** @type {import('./types.js').Signal} */ (val).f) === 'number' ); } @@ -1328,9 +1323,9 @@ export function prop_source(props_obj, key, default_value, call_default_value) { if ( is_signal(possible_signal) && - possible_signal.value === value && + possible_signal.v === value && update_bound_prop === undefined && - get_equals_method() === possible_signal.equals + get_equals_method() === possible_signal.e ) { if (should_set_default_value) { set( @@ -1372,7 +1367,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) { return; } - if (not_equal(immutable, propagating_value, source_signal.value)) { + if (not_equal(immutable, propagating_value, source_signal.v)) { ignore_next2 = true; // TODO figure out why we need it this way and the explain in a comment; // some tests fail is we just do set_signal_value(source_signal, propagating_value) @@ -1394,7 +1389,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) { return; } - if (not_equal(immutable, propagating_value, possible_signal.value)) { + if (not_equal(immutable, propagating_value, possible_signal.v)) { ignore_next1 = true; untrack(() => update_bound_prop(propagating_value)); } diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index fa9806d781ff..e3c25913173f 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -46,40 +46,45 @@ export type ComponentContext = { }; }; + +// For both SourceSignal and ComputationSignal, we use signal character property string. +// This now only reduces code-size and parsing, but it also improves the performance of the JIT compiler. +// It's likely not to have any real wins wwhen the JIT is disabled however. + export type SourceSignal = { - /** Signals that read from the current signal */ - consumers: null | ComputationSignal[]; - /** The associated component if this signal is an effect/computed */ - context: null | ComponentContext; - /** For value equality */ - equals: null | EqualsFunctions; - /** The types that the signal represent, as a bitwise value */ - flags: SignalFlags; - /** The latest value for this signal, doubles as the teardown for effects */ - value: V; + /** consumers: Signals that read from the current signal */ + c: null | ComputationSignal[]; + /** context: The associated component if this signal is an effect/computed */ + x: null | ComponentContext; + /** equals: For value equality */ + e: null | EqualsFunctions; + /** flags: The types that the signal represent, as a bitwise value */ + f: SignalFlags; + /** value: The latest value for this signal */ + v: V; }; export type ComputationSignal = { - /** The block associated with this effect/computed */ - block: null | Block; - /** Signals that read from the current signal */ - consumers: null | ComputationSignal[]; - /** The associated component if this signal is an effect/computed */ - context: null | ComponentContext; - /** Signals that this signal reads from */ - dependencies: null | Signal[]; - /** Thing(s) that need destroying */ - destroy: null | (() => void) | Array<() => void>; - /** For value equality */ - equals: null | EqualsFunctions; + /** block: The block associated with this effect/computed */ + b: null | Block; + /** consumers: Signals that read from the current signal */ + c: null | ComputationSignal[]; + /** context: The associated component if this signal is an effect/computed */ + x: null | ComponentContext; + /** dependencies: Signals that this signal reads from */ + d: null | Signal[]; + /** destroy: Thing(s) that need destroying */ + y: null | (() => void) | Array<() => void>; + /** equals: For value equality */ + e: null | EqualsFunctions; /** The types that the signal represent, as a bitwise value */ - flags: SignalFlags; - /** The function that we invoke for effects and computeds */ - init: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void)); - /** Anything that a signal owns */ - references: null | ComputationSignal[]; - /** The latest value for this signal, doubles as the teardown for effects */ - value: V; + f: SignalFlags; + /** init: The function that we invoke for effects and computeds */ + i: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void)); + /** references: Anything that a signal owns */ + r: null | ComputationSignal[]; + /** value: The latest value for this signal, doubles as the teardown for effects */ + v: V; }; export type Signal = SourceSignal | ComputationSignal; diff --git a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js index 25dcd16ae325..836fd0ed0827 100644 --- a/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js +++ b/packages/svelte/tests/snapshot/samples/class-state-field-constructor-assignment/_expected/client/index.svelte.js @@ -20,10 +20,10 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro #b = $.source(); constructor() { - this.#a.value = 1; - this.#b.value = 2; + this.#a.v = 1; + this.#b.v = 2; } } $.pop(); -} \ No newline at end of file +} From be5fa30841c67d23b6b22681d2948a5fd56a938a Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 13:00:46 +0000 Subject: [PATCH 5/8] update comment --- packages/svelte/src/internal/client/types.d.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index e3c25913173f..a8cd2a0b31a7 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -49,7 +49,10 @@ export type ComponentContext = { // For both SourceSignal and ComputationSignal, we use signal character property string. // This now only reduces code-size and parsing, but it also improves the performance of the JIT compiler. -// It's likely not to have any real wins wwhen the JIT is disabled however. +// It's likely not to have any real wins wwhen the JIT is disabled. Lastly, we keep two shapes rather than +// a single monomorphic shape to improve the memory usage. Source signals don't need the same shape as they +// simply don't do as much as computations (effects and derived signals). Thus we can improve the memory +// profile at the slight cost of some runtime performance. export type SourceSignal = { /** consumers: Signals that read from the current signal */ From 1e09e76f5a2db83d8a9174169909f00a8ef6c322 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 13:05:33 +0000 Subject: [PATCH 6/8] update comment --- packages/svelte/src/internal/client/types.d.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/svelte/src/internal/client/types.d.ts b/packages/svelte/src/internal/client/types.d.ts index a8cd2a0b31a7..1b67b0eb776a 100644 --- a/packages/svelte/src/internal/client/types.d.ts +++ b/packages/svelte/src/internal/client/types.d.ts @@ -46,7 +46,6 @@ export type ComponentContext = { }; }; - // For both SourceSignal and ComputationSignal, we use signal character property string. // This now only reduces code-size and parsing, but it also improves the performance of the JIT compiler. // It's likely not to have any real wins wwhen the JIT is disabled. Lastly, we keep two shapes rather than From c2a5941ecb884c968069fb53f84251969cd158c9 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 13:56:03 +0000 Subject: [PATCH 7/8] update comment --- packages/svelte/src/internal/client/runtime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index ae42d9c869ab..cd84e6b9aba4 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -756,7 +756,7 @@ export function get(signal) { current_dependencies_index++; } else if (current_dependencies === null) { current_dependencies = [signal]; - } else if (signal !== current_dependencies.at(-1)) { + } else if (signal !== current_dependencies[current_dependencies.length - 1]) { current_dependencies.push(signal); } } From f8b892e0292608afe1272656475e552269e403c5 Mon Sep 17 00:00:00 2001 From: Dominic Gannaway Date: Sat, 18 Nov 2023 14:02:01 +0000 Subject: [PATCH 8/8] feedback --- packages/svelte/src/internal/client/runtime.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/svelte/src/internal/client/runtime.js b/packages/svelte/src/internal/client/runtime.js index cd84e6b9aba4..3800b9021586 100644 --- a/packages/svelte/src/internal/client/runtime.js +++ b/packages/svelte/src/internal/client/runtime.js @@ -873,7 +873,8 @@ export function mutate_store(store, expression, new_value) { */ export function mark_subtree_inert(signal, inert) { const flags = signal.f; - if (((flags & INERT) === 0 && inert) || ((flags & INERT) !== 0 && !inert)) { + const is_already_inert = (flags & INERT) !== 0; + if (is_already_inert !== inert) { signal.f ^= INERT; if (!inert && (flags & IS_EFFECT) !== 0 && (flags & CLEAN) === 0) { schedule_effect(/** @type {import('./types.js').EffectSignal} */ (signal), false);