Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hungry-dots-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: more signal perf tuning
13 changes: 4 additions & 9 deletions packages/svelte/src/internal/client/render.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import {
EACH_IS_CONTROLLED,
EACH_INDEX_REACTIVE,
EACH_ITEM_REACTIVE,
EACH_IS_ANIMATED,
PassiveDelegatedEvents,
DelegatedEvents
} from '../../constants.js';
Expand Down Expand Up @@ -624,7 +623,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(() => {
Expand Down Expand Up @@ -2125,7 +2124,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));
}
}

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -3163,7 +3158,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));
}
];
}
Expand Down
82 changes: 53 additions & 29 deletions packages/svelte/src/internal/client/runtime.js
Original file line number Diff line number Diff line change
Expand Up @@ -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} */
Expand Down Expand Up @@ -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<V>}
*/
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<V>}
* @returns {import('./types.js').ComputationSignal<V>}
*/
function create_signal_object(flags, value, block) {
function create_computation_signal(flags, value, block) {
return {
block,
consumers: null,
Expand All @@ -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) {
Expand All @@ -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<V>} **/ (signal).dependencies;
if (dependencies !== null) {
const length = dependencies.length;
let i;
Expand All @@ -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<V>} **/ (dependency), true);
// Might have been mutated from above get.
if ((signal.flags & DIRTY) !== 0) {
return true;
Expand All @@ -212,7 +229,7 @@ function is_signal_dirty(signal) {

/**
* @template V
* @param {import('./types.js').Signal<V>} signal
* @param {import('./types.js').ComputationSignal<V>} signal
* @returns {V}
*/
function execute_signal_fn(signal) {
Expand Down Expand Up @@ -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<unknown>[]} **/ (signal.dependencies);

if (current_dependencies !== null) {
let i;
Expand All @@ -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<V>[]} **/ (dependencies = current_dependencies);
}

if (!current_skip_consumer) {
Expand Down Expand Up @@ -291,7 +308,7 @@ function execute_signal_fn(signal) {

/**
* @template V
* @param {import('./types.js').Signal<V>} signal
* @param {import('./types.js').ComputationSignal<V>} signal
* @param {number} start_index
* @param {boolean} remove_unowned
* @returns {void}
Expand All @@ -316,15 +333,19 @@ 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<V>} **/ (dependency),
0,
true
);
}
}
}
}

/**
* @template V
* @param {import('./types.js').Signal<V>} signal
* @param {import('./types.js').ComputationSignal<V>} signal
* @returns {void}
*/
function destroy_references(signal) {
Expand Down Expand Up @@ -575,7 +596,7 @@ export async function tick() {

/**
* @template V
* @param {import('./types.js').Signal<V>} signal
* @param {import('./types.js').ComputationSignal<V>} signal
* @param {boolean} force_schedule
* @returns {void}
*/
Expand Down Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
}
});
}
Expand Down Expand Up @@ -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<V>} **/ (signal), false);
}
return signal.value;
}
Expand Down Expand Up @@ -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}
*/
Expand Down Expand Up @@ -969,12 +992,13 @@ export function set_signal_value(signal, value) {

/**
* @template V
* @param {import('./types.js').Signal<V>} signal
* @param {import('./types.js').ComputationSignal<V>} 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;
Expand Down Expand Up @@ -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<V>}
* @returns {import('./types.js').ComputationSignal<V>}
*/
/*#__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<V>} */ (
create_signal_object(flags | CLEAN, UNINITIALIZED, current_block)
const signal = /** @type {import('./types.js').ComputationSignal<V>} */ (
create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
);
signal.init = init;
signal.context = current_component_context;
Expand All @@ -1027,11 +1051,11 @@ export function derived(init, equals) {
* @template V
* @param {V} initial_value
* @param {import('./types.js').EqualsFunctions<V>} [equals]
* @returns {import('./types.js').Signal<V>}
* @returns {import('./types.js').SourceSignal<V>}
*/
/*#__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;
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -1210,7 +1234,7 @@ export function managed_render_effect(init, block = current_block, sync = true)

/**
* @template V
* @param {import('./types.js').Signal<V>} signal
* @param {import('./types.js').ComputationSignal<V>} signal
* @param {() => void} destroy_fn
* @returns {void}
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/svelte/src/internal/client/transitions.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<unknown>} effect
* @param {import('./types.js').EffectSignal} effect
* @returns {import('./types.js').Transition}
*/
function create_transition(dom, init, direction, effect) {
Expand Down
Loading