Skip to content

Commit cfdf865

Browse files
committed
chore: opt for two signal data-structures to reduce memory usage
1 parent 40e2c92 commit cfdf865

File tree

5 files changed

+93
-49
lines changed

5 files changed

+93
-49
lines changed

.changeset/hungry-dots-fry.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: more signal perf tuning

packages/svelte/src/internal/client/render.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -624,7 +624,7 @@ export function bind_playback_rate(media, get_value, update) {
624624
// Needs to happen after the element is inserted into the dom, else playback will be set back to 1 by the browser.
625625
// For hydration we could do it immediately but the additional code is not worth the lost microtask.
626626

627-
/** @type {import('./types.js').Signal | undefined} */
627+
/** @type {import('./types.js').ComputationSignal | undefined} */
628628
let render;
629629
let destroyed = false;
630630
const effect = managed_effect(() => {
@@ -2125,7 +2125,7 @@ export function destroy_each_item_block(
21252125
if (!controlled && dom !== null) {
21262126
remove(dom);
21272127
}
2128-
destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect));
2128+
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect));
21292129
}
21302130
}
21312131

@@ -3163,7 +3163,7 @@ export function mount(component, options) {
31633163
if (hydration_fragment !== null) {
31643164
remove(hydration_fragment);
31653165
}
3166-
destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect));
3166+
destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect));
31673167
}
31683168
];
31693169
}

packages/svelte/src/internal/client/runtime.js

Lines changed: 53 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ let current_queued_tasks = [];
4646
let flush_count = 0;
4747
// Handle signal reactivity tree dependencies and consumer
4848

49-
/** @type {null | import('./types.js').Signal} */
49+
/** @type {null | import('./types.js').ComputationSignal} */
5050
let current_consumer = null;
5151

5252
/** @type {null | import('./types.js').EffectSignal} */
@@ -133,14 +133,31 @@ function default_equals(a, b) {
133133
return a === b;
134134
}
135135

136+
/**
137+
* @template V
138+
* @param {import('./types.js').SignalFlags} flags
139+
* @param {V} value
140+
* @returns {import('./types.js').SourceSignal<V>}
141+
*/
142+
function create_source_signal(flags, value) {
143+
return {
144+
consumers: null,
145+
// We can remove this if we get rid of beforeUpdate/afterUpdate
146+
context: null,
147+
equals: null,
148+
flags,
149+
value
150+
};
151+
}
152+
136153
/**
137154
* @template V
138155
* @param {import('./types.js').SignalFlags} flags
139156
* @param {V} value
140157
* @param {import('./types.js').Block | null} block
141-
* @returns {import('./types.js').Signal<V>}
158+
* @returns {import('./types.js').ComputationSignal<V>}
142159
*/
143-
function create_signal_object(flags, value, block) {
160+
function create_computation_signal(flags, value, block) {
144161
return {
145162
block,
146163
consumers: null,
@@ -156,8 +173,8 @@ function create_signal_object(flags, value, block) {
156173
}
157174

158175
/**
159-
* @param {import('./types.js').Signal} target_signal
160-
* @param {import('./types.js').Signal} ref_signal
176+
* @param {import('./types.js').ComputationSignal} target_signal
177+
* @param {import('./types.js').ComputationSignal} ref_signal
161178
* @returns {void}
162179
*/
163180
function push_reference(target_signal, ref_signal) {
@@ -180,7 +197,7 @@ function is_signal_dirty(signal) {
180197
return true;
181198
}
182199
if ((flags & MAYBE_DIRTY) !== 0) {
183-
const dependencies = signal.dependencies;
200+
const dependencies = /** @type {import('./types.js').ComputationSignal<V>} **/ (signal).dependencies;
184201
if (dependencies !== null) {
185202
const length = dependencies.length;
186203
let i;
@@ -195,7 +212,7 @@ function is_signal_dirty(signal) {
195212
// The flags can be marked as dirty from the above is_signal_dirty call.
196213
if ((dependency.flags & DIRTY) !== 0) {
197214
if ((dep_flags & DERIVED) !== 0) {
198-
update_derived(dependency, true);
215+
update_derived(/** @type {import('./types.js').ComputationSignal<V>} **/ (dependency), true);
199216
// Might have been mutated from above get.
200217
if ((signal.flags & DIRTY) !== 0) {
201218
return true;
@@ -212,7 +229,7 @@ function is_signal_dirty(signal) {
212229

213230
/**
214231
* @template V
215-
* @param {import('./types.js').Signal<V>} signal
232+
* @param {import('./types.js').ComputationSignal<V>} signal
216233
* @returns {V}
217234
*/
218235
function execute_signal_fn(signal) {
@@ -247,7 +264,7 @@ function execute_signal_fn(signal) {
247264
} else {
248265
res = /** @type {() => V} */ (init)();
249266
}
250-
let dependencies = signal.dependencies;
267+
let dependencies = /** @type {import('./types.js').Signal<unknown>[]} **/ (signal.dependencies);
251268

252269
if (current_dependencies !== null) {
253270
let i;
@@ -259,7 +276,7 @@ function execute_signal_fn(signal) {
259276
dependencies[current_dependencies_index + i] = current_dependencies[i];
260277
}
261278
} else {
262-
signal.dependencies = dependencies = current_dependencies;
279+
signal.dependencies = /** @type {import('./types.js').Signal<V>[]} **/ (dependencies = current_dependencies);
263280
}
264281

265282
if (!current_skip_consumer) {
@@ -291,7 +308,7 @@ function execute_signal_fn(signal) {
291308

292309
/**
293310
* @template V
294-
* @param {import('./types.js').Signal<V>} signal
311+
* @param {import('./types.js').ComputationSignal<V>} signal
295312
* @param {number} start_index
296313
* @param {boolean} remove_unowned
297314
* @returns {void}
@@ -316,15 +333,19 @@ function remove_consumer(signal, start_index, remove_unowned) {
316333
}
317334
}
318335
if (remove_unowned && consumers_length === 0 && (dependency.flags & UNOWNED) !== 0) {
319-
remove_consumer(dependency, 0, true);
336+
remove_consumer(
337+
/** @type {import('./types.js').ComputationSignal<V>} **/ (dependency),
338+
0,
339+
true
340+
);
320341
}
321342
}
322343
}
323344
}
324345

325346
/**
326347
* @template V
327-
* @param {import('./types.js').Signal<V>} signal
348+
* @param {import('./types.js').ComputationSignal<V>} signal
328349
* @returns {void}
329350
*/
330351
function destroy_references(signal) {
@@ -575,7 +596,7 @@ export async function tick() {
575596

576597
/**
577598
* @template V
578-
* @param {import('./types.js').Signal<V>} signal
599+
* @param {import('./types.js').ComputationSignal<V>} signal
579600
* @param {boolean} force_schedule
580601
* @returns {void}
581602
*/
@@ -615,10 +636,11 @@ export function store_get(store, store_name, stores) {
615636
value: source(UNINITIALIZED),
616637
unsubscribe: EMPTY_FUNC
617638
};
618-
push_destroy_fn(entry.value, () => {
619-
/** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value =
620-
/** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value;
621-
});
639+
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
640+
// push_destroy_fn(entry.value, () => {
641+
// /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value =
642+
// /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value;
643+
// });
622644
stores[store_name] = entry;
623645
}
624646

@@ -676,7 +698,8 @@ export function unsubscribe_on_destroy(stores) {
676698
for (store_name in stores) {
677699
const ref = stores[store_name];
678700
ref.unsubscribe();
679-
destroy_signal(ref.value);
701+
// TODO: can we remove this code? it was refactored out when we split up source/comptued signals
702+
// destroy_signal(ref.value);
680703
}
681704
});
682705
}
@@ -740,7 +763,7 @@ export function get(signal) {
740763
}
741764

742765
if ((flags & DERIVED) !== 0 && is_signal_dirty(signal)) {
743-
update_derived(signal, false);
766+
update_derived(/** @type {import('./types.js').ComputationSignal<V>} **/ (signal), false);
744767
}
745768
return signal.value;
746769
}
@@ -845,7 +868,7 @@ export function mutate_store(store, expression, new_value) {
845868
}
846869

847870
/**
848-
* @param {import('./types.js').Signal} signal
871+
* @param {import('./types.js').ComputationSignal} signal
849872
* @param {boolean} inert
850873
* @returns {void}
851874
*/
@@ -969,12 +992,13 @@ export function set_signal_value(signal, value) {
969992

970993
/**
971994
* @template V
972-
* @param {import('./types.js').Signal<V>} signal
995+
* @param {import('./types.js').ComputationSignal<V>} signal
973996
* @returns {void}
974997
*/
975998
export function destroy_signal(signal) {
976999
const teardown = /** @type {null | (() => void)} */ (signal.value);
9771000
const destroy = signal.destroy;
1001+
const flags = signal.flags;
9781002
destroy_references(signal);
9791003
remove_consumer(signal, 0, true);
9801004
signal.init = null;
@@ -1005,14 +1029,14 @@ export function destroy_signal(signal) {
10051029
* @template V
10061030
* @param {() => V} init
10071031
* @param {import('./types.js').EqualsFunctions} [equals]
1008-
* @returns {import('./types.js').Signal<V>}
1032+
* @returns {import('./types.js').ComputationSignal<V>}
10091033
*/
10101034
/*#__NO_SIDE_EFFECTS__*/
10111035
export function derived(init, equals) {
10121036
const is_unowned = current_effect === null;
10131037
const flags = is_unowned ? DERIVED | UNOWNED : DERIVED;
1014-
const signal = /** @type {import('./types.js').Signal<V>} */ (
1015-
create_signal_object(flags | CLEAN, UNINITIALIZED, current_block)
1038+
const signal = /** @type {import('./types.js').ComputationSignal<V>} */ (
1039+
create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
10161040
);
10171041
signal.init = init;
10181042
signal.context = current_component_context;
@@ -1027,11 +1051,11 @@ export function derived(init, equals) {
10271051
* @template V
10281052
* @param {V} initial_value
10291053
* @param {import('./types.js').EqualsFunctions<V>} [equals]
1030-
* @returns {import('./types.js').Signal<V>}
1054+
* @returns {import('./types.js').SourceSignal<V>}
10311055
*/
10321056
/*#__NO_SIDE_EFFECTS__*/
10331057
export function source(initial_value, equals) {
1034-
const source = create_signal_object(SOURCE | CLEAN, initial_value, null);
1058+
const source = create_source_signal(SOURCE | CLEAN, initial_value);
10351059
source.context = current_component_context;
10361060
source.equals = get_equals_method(equals);
10371061
return source;
@@ -1079,7 +1103,7 @@ export function untrack(fn) {
10791103
* @returns {import('./types.js').EffectSignal}
10801104
*/
10811105
function internal_create_effect(type, init, sync, block, schedule) {
1082-
const signal = create_signal_object(type | DIRTY, null, block);
1106+
const signal = create_computation_signal(type | DIRTY, null, block);
10831107
signal.init = init;
10841108
signal.context = current_component_context;
10851109
if (schedule) {
@@ -1210,7 +1234,7 @@ export function managed_render_effect(init, block = current_block, sync = true)
12101234

12111235
/**
12121236
* @template V
1213-
* @param {import('./types.js').Signal<V>} signal
1237+
* @param {import('./types.js').ComputationSignal<V>} signal
12141238
* @param {() => void} destroy_fn
12151239
* @returns {void}
12161240
*/

packages/svelte/src/internal/client/transitions.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ function handle_raf(time) {
253253
* @param {HTMLElement} dom
254254
* @param {() => import('./types.js').TransitionPayload} init
255255
* @param {'in' | 'out' | 'both' | 'key'} direction
256-
* @param {import('./types.js').Signal<unknown>} effect
256+
* @param {import('./types.js').EffectSignal} effect
257257
* @returns {import('./types.js').Transition}
258258
*/
259259
function create_transition(dom, init, direction, effect) {

0 commit comments

Comments
 (0)