chore: more signal fine-tuning (#9531)

* chore: opt for two signal data-structures to reduce memory usage
pull/9530/head
Dominic Gannaway 11 months ago committed by GitHub
parent bbd1a6c05a
commit 298da65ed6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,5 @@
---
'svelte': patch
---
chore: more signal perf tuning

@ -14,21 +14,21 @@ export const global_visitors = {
}, },
MemberExpression(node, { state, next }) { MemberExpression(node, { state, next }) {
if (node.object.type === 'ThisExpression') { 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') { if (node.property.type === 'PrivateIdentifier') {
const field = state.private_state.get(node.property.name); const field = state.private_state.get(node.property.name);
if (field) { 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) { if (node.property.type === 'Identifier' && !node.computed) {
const field = state.public_state.get(node.property.name); const field = state.public_state.get(node.property.name);
if (field && state.in_constructor) { 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'));
} }
} }
} }

@ -26,7 +26,6 @@ import {
EACH_IS_CONTROLLED, EACH_IS_CONTROLLED,
EACH_INDEX_REACTIVE, EACH_INDEX_REACTIVE,
EACH_ITEM_REACTIVE, EACH_ITEM_REACTIVE,
EACH_IS_ANIMATED,
PassiveDelegatedEvents, PassiveDelegatedEvents,
DelegatedEvents DelegatedEvents
} from '../../constants.js'; } from '../../constants.js';
@ -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. // 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. // 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 render;
let destroyed = false; let destroyed = false;
const effect = managed_effect(() => { const effect = managed_effect(() => {
@ -2083,7 +2082,7 @@ export function update_each_item_block(block, item, index, type) {
if (transitions !== null && (type & EACH_KEYED) !== 0) { if (transitions !== null && (type & EACH_KEYED) !== 0) {
let prev_index = block.index; let prev_index = block.index;
if (index_is_reactive) { if (index_is_reactive) {
prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).value; prev_index = /** @type {import('./types.js').Signal<number>} */ (prev_index).v;
} }
const items = block.parent.items; const items = block.parent.items;
if (prev_index !== index && /** @type {number} */ (index) < items.length) { if (prev_index !== index && /** @type {number} */ (index) < items.length) {
@ -2125,7 +2124,7 @@ export function destroy_each_item_block(
if (!controlled && dom !== null) { if (!controlled && dom !== null) {
remove(dom); remove(dom);
} }
destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect)); destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect));
} }
} }
@ -2244,11 +2243,7 @@ function each(anchor_node, collection, flags, key_fn, render_fn, fallback_fn, re
? [] ? []
: Array.from(maybe_array); : Array.from(maybe_array);
if (key_fn !== null) { if (key_fn !== null) {
const length = array.length; keys = array.map(key_fn);
keys = Array(length);
for (let i = 0; i < length; i++) {
keys[i] = key_fn(array[i]);
}
} }
if (fallback_fn !== null) { if (fallback_fn !== null) {
if (array.length === 0) { if (array.length === 0) {
@ -3163,7 +3158,7 @@ export function mount(component, options) {
if (hydration_fragment !== null) { if (hydration_fragment !== null) {
remove(hydration_fragment); remove(hydration_fragment);
} }
destroy_signal(/** @type {import('./types.js').Signal} */ (block.effect)); destroy_signal(/** @type {import('./types.js').EffectSignal} */ (block.effect));
} }
]; ];
} }

@ -1,7 +1,6 @@
import { subscribe_to_store } from '../../store/utils.js'; import { subscribe_to_store } from '../../store/utils.js';
import { EMPTY_FUNC } from '../common.js'; import { EMPTY_FUNC } from '../common.js';
import { unwrap } from './render.js'; import { unwrap } from './render.js';
import { map_delete, map_get, map_set } from './operations.js';
import { is_array } from './utils.js'; import { is_array } from './utils.js';
export const SOURCE = 1; export const SOURCE = 1;
@ -46,7 +45,7 @@ let current_queued_tasks = [];
let flush_count = 0; let flush_count = 0;
// Handle signal reactivity tree dependencies and consumer // Handle signal reactivity tree dependencies and consumer
/** @type {null | import('./types.js').Signal} */ /** @type {null | import('./types.js').ComputationSignal} */
let current_consumer = null; let current_consumer = null;
/** @type {null | import('./types.js').EffectSignal} */ /** @type {null | import('./types.js').EffectSignal} */
@ -133,37 +132,54 @@ function default_equals(a, b) {
return 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 {
c: null,
// We can remove this if we get rid of beforeUpdate/afterUpdate
x: null,
e: null,
f: flags,
v: value
};
}
/** /**
* @template V * @template V
* @param {import('./types.js').SignalFlags} flags * @param {import('./types.js').SignalFlags} flags
* @param {V} value * @param {V} value
* @param {import('./types.js').Block | null} block * @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 { return {
block, b: block,
consumers: null, c: null,
context: null, x: null,
dependencies: null, d: null,
destroy: null, y: null,
equals: null, e: null,
flags, f: flags,
init: null, i: null,
references: null, r: null,
value v: value
}; };
} }
/** /**
* @param {import('./types.js').Signal} target_signal * @param {import('./types.js').ComputationSignal} target_signal
* @param {import('./types.js').Signal} ref_signal * @param {import('./types.js').ComputationSignal} ref_signal
* @returns {void} * @returns {void}
*/ */
function push_reference(target_signal, ref_signal) { function push_reference(target_signal, ref_signal) {
const references = target_signal.references; const references = target_signal.r;
if (references === null) { if (references === null) {
target_signal.references = [ref_signal]; target_signal.r = [ref_signal];
} else { } else {
references.push(ref_signal); references.push(ref_signal);
} }
@ -175,29 +191,31 @@ function push_reference(target_signal, ref_signal) {
* @returns {boolean} * @returns {boolean}
*/ */
function is_signal_dirty(signal) { function is_signal_dirty(signal) {
const flags = signal.flags; const flags = signal.f;
if ((flags & DIRTY) !== 0 || signal.value === UNINITIALIZED) { if ((flags & DIRTY) !== 0 || signal.v === UNINITIALIZED) {
return true; return true;
} }
if ((flags & MAYBE_DIRTY) !== 0) { if ((flags & MAYBE_DIRTY) !== 0) {
const dependencies = signal.dependencies; const dependencies = /** @type {import('./types.js').ComputationSignal<V>} **/ (signal).d;
if (dependencies !== null) { if (dependencies !== null) {
const length = dependencies.length; const length = dependencies.length;
let i; let i;
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
const dependency = dependencies[i]; const dependency = dependencies[i];
const dep_flags = dependency.flags;
if ((dep_flags & MAYBE_DIRTY) !== 0 && !is_signal_dirty(dependency)) { if ((dependency.f & MAYBE_DIRTY) !== 0 && !is_signal_dirty(dependency)) {
set_signal_status(dependency, CLEAN); set_signal_status(dependency, CLEAN);
continue; continue;
} }
// The flags can be marked as dirty from the above is_signal_dirty call. // The flags can be marked as dirty from the above is_signal_dirty call.
if ((dependency.flags & DIRTY) !== 0) { if ((dependency.f & DIRTY) !== 0) {
if ((dep_flags & DERIVED) !== 0) { if ((dependency.f & DERIVED) !== 0) {
update_derived(dependency, true); update_derived(
/** @type {import('./types.js').ComputationSignal<V>} **/ (dependency),
true
);
// Might have been mutated from above get. // Might have been mutated from above get.
if ((signal.flags & DIRTY) !== 0) { if ((signal.f & DIRTY) !== 0) {
return true; return true;
} }
} else { } else {
@ -212,25 +230,25 @@ function is_signal_dirty(signal) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @returns {V} * @returns {V}
*/ */
function execute_signal_fn(signal) { function execute_signal_fn(signal) {
const init = signal.init; const init = signal.i;
const previous_dependencies = current_dependencies; const previous_dependencies = current_dependencies;
const previous_dependencies_index = current_dependencies_index; const previous_dependencies_index = current_dependencies_index;
const previous_consumer = current_consumer; const previous_consumer = current_consumer;
const previous_block = current_block; const previous_block = current_block;
const previous_component_context = current_component_context; const previous_component_context = current_component_context;
const previous_skip_consumer = current_skip_consumer; 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; const previous_untracking = current_untracking;
current_dependencies = /** @type {null | import('./types.js').Signal[]} */ (null); current_dependencies = /** @type {null | import('./types.js').Signal[]} */ (null);
current_dependencies_index = 0; current_dependencies_index = 0;
current_consumer = signal; current_consumer = signal;
current_block = signal.block; current_block = signal.b;
current_component_context = signal.context; current_component_context = signal.x;
current_skip_consumer = current_effect === null && (signal.flags & UNOWNED) !== 0; current_skip_consumer = current_effect === null && (signal.f & UNOWNED) !== 0;
current_untracking = false; current_untracking = false;
// Render effects are invoked when the UI is about to be updated - run beforeUpdate at that point // Render effects are invoked when the UI is about to be updated - run beforeUpdate at that point
@ -242,12 +260,12 @@ function execute_signal_fn(signal) {
let res; let res;
if (is_render_effect) { if (is_render_effect) {
res = /** @type {(block: import('./types.js').Block) => V} */ (init)( res = /** @type {(block: import('./types.js').Block) => V} */ (init)(
/** @type {import('./types.js').Block} */ (signal.block) /** @type {import('./types.js').Block} */ (signal.b)
); );
} else { } else {
res = /** @type {() => V} */ (init)(); res = /** @type {() => V} */ (init)();
} }
let dependencies = signal.dependencies; let dependencies = /** @type {import('./types.js').Signal<unknown>[]} **/ (signal.d);
if (current_dependencies !== null) { if (current_dependencies !== null) {
let i; let i;
@ -259,17 +277,19 @@ function execute_signal_fn(signal) {
dependencies[current_dependencies_index + i] = current_dependencies[i]; dependencies[current_dependencies_index + i] = current_dependencies[i];
} }
} else { } else {
signal.dependencies = dependencies = current_dependencies; signal.d = /** @type {import('./types.js').Signal<V>[]} **/ (
dependencies = current_dependencies
);
} }
if (!current_skip_consumer) { if (!current_skip_consumer) {
for (i = current_dependencies_index; i < dependencies.length; i++) { for (i = current_dependencies_index; i < dependencies.length; i++) {
const dependency = dependencies[i]; const dependency = dependencies[i];
if (dependency.consumers === null) { if (dependency.c === null) {
dependency.consumers = [signal]; dependency.c = [signal];
} else { } else {
dependency.consumers.push(signal); dependency.c.push(signal);
} }
} }
} }
@ -291,23 +311,23 @@ function execute_signal_fn(signal) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @param {number} start_index * @param {number} start_index
* @param {boolean} remove_unowned * @param {boolean} remove_unowned
* @returns {void} * @returns {void}
*/ */
function remove_consumer(signal, start_index, remove_unowned) { function remove_consumer(signal, start_index, remove_unowned) {
const dependencies = signal.dependencies; const dependencies = signal.d;
if (dependencies !== null) { if (dependencies !== null) {
let i; let i;
for (i = start_index; i < dependencies.length; i++) { for (i = start_index; i < dependencies.length; i++) {
const dependency = dependencies[i]; const dependency = dependencies[i];
const consumers = dependency.consumers; const consumers = dependency.c;
let consumers_length = 0; let consumers_length = 0;
if (consumers !== null) { if (consumers !== null) {
consumers_length = consumers.length - 1; consumers_length = consumers.length - 1;
if (consumers_length === 0) { if (consumers_length === 0) {
dependency.consumers = null; dependency.c = null;
} else { } else {
const index = consumers.indexOf(signal); const index = consumers.indexOf(signal);
// Swap with last element and then remove. // Swap with last element and then remove.
@ -315,8 +335,12 @@ function remove_consumer(signal, start_index, remove_unowned) {
consumers.pop(); consumers.pop();
} }
} }
if (remove_unowned && consumers_length === 0 && (dependency.flags & UNOWNED) !== 0) { if (remove_unowned && consumers_length === 0 && (dependency.f & UNOWNED) !== 0) {
remove_consumer(dependency, 0, true); remove_consumer(
/** @type {import('./types.js').ComputationSignal<V>} **/ (dependency),
0,
true
);
} }
} }
} }
@ -324,21 +348,21 @@ function remove_consumer(signal, start_index, remove_unowned) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @returns {void} * @returns {void}
*/ */
function destroy_references(signal) { function destroy_references(signal) {
const references = signal.references; const references = signal.r;
signal.references = null; signal.r = null;
if (references !== null) { if (references !== null) {
let i; let i;
for (i = 0; i < references.length; i++) { for (i = 0; i < references.length; i++) {
const reference = references[i]; const reference = references[i];
if ((reference.flags & IS_EFFECT) !== 0) { if ((reference.f & IS_EFFECT) !== 0) {
destroy_signal(reference); destroy_signal(reference);
} else { } else {
remove_consumer(reference, 0, true); remove_consumer(reference, 0, true);
reference.dependencies = null; reference.d = null;
} }
} }
} }
@ -363,10 +387,10 @@ function report_error(block, error) {
* @returns {void} * @returns {void}
*/ */
export function execute_effect(signal) { export function execute_effect(signal) {
if ((signal.flags & DESTROYED) !== 0) { if ((signal.f & DESTROYED) !== 0) {
return; return;
} }
const teardown = signal.value; const teardown = signal.v;
const previous_effect = current_effect; const previous_effect = current_effect;
current_effect = signal; current_effect = signal;
@ -377,10 +401,10 @@ export function execute_effect(signal) {
} }
const possible_teardown = execute_signal_fn(signal); const possible_teardown = execute_signal_fn(signal);
if (typeof possible_teardown === 'function') { if (typeof possible_teardown === 'function') {
signal.value = possible_teardown; signal.v = possible_teardown;
} }
} catch (error) { } catch (error) {
const block = signal.block; const block = signal.b;
if (block !== null) { if (block !== null) {
report_error(block, error); report_error(block, error);
} else { } else {
@ -389,10 +413,10 @@ export function execute_effect(signal) {
} finally { } finally {
current_effect = previous_effect; current_effect = previous_effect;
} }
const component_context = signal.context; const component_context = signal.x;
if ( if (
is_runes(component_context) && // Don't rerun pre effects more than once to accomodate for "$: only runs once" behavior 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 current_queued_pre_and_render_effects.length > 0
) { ) {
flush_local_pre_effects(component_context); flush_local_pre_effects(component_context);
@ -416,7 +440,7 @@ function flush_queued_effects(effects) {
let i; let i;
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
const signal = effects[i]; const signal = effects[i];
const flags = signal.flags; const flags = signal.f;
if ((flags & (DESTROYED | INERT)) === 0) { if ((flags & (DESTROYED | INERT)) === 0) {
if (is_signal_dirty(signal)) { if (is_signal_dirty(signal)) {
set_signal_status(signal, CLEAN); set_signal_status(signal, CLEAN);
@ -452,7 +476,7 @@ function process_microtask() {
* @returns {void} * @returns {void}
*/ */
export function schedule_effect(signal, sync) { export function schedule_effect(signal, sync) {
const flags = signal.flags; const flags = signal.f;
if (sync || (flags & SYNC_EFFECT) !== 0) { if (sync || (flags & SYNC_EFFECT) !== 0) {
execute_effect(signal); execute_effect(signal);
set_signal_status(signal, CLEAN); set_signal_status(signal, CLEAN);
@ -499,7 +523,7 @@ export function flush_local_render_effects() {
const effects = []; const effects = [];
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) { for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) {
const effect = current_queued_pre_and_render_effects[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); effects.push(effect);
current_queued_pre_and_render_effects.splice(i, 1); current_queued_pre_and_render_effects.splice(i, 1);
i--; i--;
@ -516,7 +540,7 @@ export function flush_local_pre_effects(context) {
const effects = []; const effects = [];
for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) { for (let i = 0; i < current_queued_pre_and_render_effects.length; i++) {
const effect = current_queued_pre_and_render_effects[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); effects.push(effect);
current_queued_pre_and_render_effects.splice(i, 1); current_queued_pre_and_render_effects.splice(i, 1);
i--; i--;
@ -575,20 +599,20 @@ export async function tick() {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @param {boolean} force_schedule * @param {boolean} force_schedule
* @returns {void} * @returns {void}
*/ */
function update_derived(signal, force_schedule) { function update_derived(signal, force_schedule) {
const value = execute_signal_fn(signal); const value = execute_signal_fn(signal);
const status = const status =
current_skip_consumer || (current_effect === null && (signal.flags & UNOWNED) !== 0) current_skip_consumer || (current_effect === null && (signal.f & UNOWNED) !== 0)
? DIRTY ? DIRTY
: CLEAN; : CLEAN;
set_signal_status(signal, status); set_signal_status(signal, status);
const equals = /** @type {import('./types.js').EqualsFunctions} */ (signal.equals); const equals = /** @type {import('./types.js').EqualsFunctions} */ (signal.e);
if (!equals(value, signal.value)) { if (!equals(value, signal.v)) {
signal.value = value; signal.v = value;
mark_signal_consumers(signal, DIRTY, force_schedule); mark_signal_consumers(signal, DIRTY, force_schedule);
} }
} }
@ -615,10 +639,11 @@ export function store_get(store, store_name, stores) {
value: source(UNINITIALIZED), value: source(UNINITIALIZED),
unsubscribe: EMPTY_FUNC unsubscribe: EMPTY_FUNC
}; };
push_destroy_fn(entry.value, () => { // TODO: can we remove this code? it was refactored out when we split up source/comptued signals
/** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value = // push_destroy_fn(entry.value, () => {
/** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value; // /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).last_value =
}); // /** @type {import('./types.js').StoreReferencesContainer['']} */ (entry).value.value;
// });
stores[store_name] = entry; stores[store_name] = entry;
} }
@ -676,7 +701,8 @@ export function unsubscribe_on_destroy(stores) {
for (store_name in stores) { for (store_name in stores) {
const ref = stores[store_name]; const ref = stores[store_name];
ref.unsubscribe(); 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);
} }
}); });
} }
@ -704,9 +730,9 @@ export function exposable(fn) {
* @returns {V} * @returns {V}
*/ */
export function get(signal) { export function get(signal) {
const flags = signal.flags; const flags = signal.f;
if ((flags & DESTROYED) !== 0) { if ((flags & DESTROYED) !== 0) {
return signal.value; return signal.v;
} }
if (is_signal_exposed && current_should_capture_signal) { if (is_signal_exposed && current_should_capture_signal) {
@ -718,13 +744,9 @@ export function get(signal) {
} }
// Register the dependency on the current consumer signal. // Register the dependency on the current consumer signal.
if ( if (current_consumer !== null && (current_consumer.f & MANAGED) === 0 && !current_untracking) {
current_consumer !== null && const unowned = (current_consumer.f & UNOWNED) !== 0;
(current_consumer.flags & MANAGED) === 0 && const dependencies = current_consumer.d;
!current_untracking
) {
const unowned = (current_consumer.flags & UNOWNED) !== 0;
const dependencies = current_consumer.dependencies;
if ( if (
current_dependencies === null && current_dependencies === null &&
dependencies !== null && dependencies !== null &&
@ -734,15 +756,15 @@ export function get(signal) {
current_dependencies_index++; current_dependencies_index++;
} else if (current_dependencies === null) { } else if (current_dependencies === null) {
current_dependencies = [signal]; current_dependencies = [signal];
} else if (signal !== current_dependencies.at(-1)) { } else if (signal !== current_dependencies[current_dependencies.length - 1]) {
current_dependencies.push(signal); current_dependencies.push(signal);
} }
} }
if ((flags & DERIVED) !== 0 && is_signal_dirty(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; return signal.v;
} }
/** /**
@ -845,19 +867,20 @@ export function mutate_store(store, expression, new_value) {
} }
/** /**
* @param {import('./types.js').Signal} signal * @param {import('./types.js').ComputationSignal} signal
* @param {boolean} inert * @param {boolean} inert
* @returns {void} * @returns {void}
*/ */
export function mark_subtree_inert(signal, inert) { export function mark_subtree_inert(signal, inert) {
const flags = signal.flags; const flags = signal.f;
if (((flags & INERT) === 0 && inert) || ((flags & INERT) !== 0 && !inert)) { const is_already_inert = (flags & INERT) !== 0;
signal.flags ^= INERT; if (is_already_inert !== inert) {
signal.f ^= INERT;
if (!inert && (flags & IS_EFFECT) !== 0 && (flags & CLEAN) === 0) { if (!inert && (flags & IS_EFFECT) !== 0 && (flags & CLEAN) === 0) {
schedule_effect(/** @type {import('./types.js').EffectSignal} */ (signal), false); schedule_effect(/** @type {import('./types.js').EffectSignal} */ (signal), false);
} }
} }
const references = signal.references; const references = signal.r;
if (references !== null) { if (references !== null) {
let i; let i;
for (i = 0; i < references.length; i++) { for (i = 0; i < references.length; i++) {
@ -874,14 +897,14 @@ export function mark_subtree_inert(signal, inert) {
* @returns {void} * @returns {void}
*/ */
function mark_signal_consumers(signal, to_status, force_schedule) { function mark_signal_consumers(signal, to_status, force_schedule) {
const runes = is_runes(signal.context); const runes = is_runes(signal.x);
const consumers = signal.consumers; const consumers = signal.c;
if (consumers !== null) { if (consumers !== null) {
const length = consumers.length; const length = consumers.length;
let i; let i;
for (i = 0; i < length; i++) { for (i = 0; i < length; i++) {
const consumer = consumers[i]; const consumer = consumers[i];
const flags = consumer.flags; const flags = consumer.f;
const unowned = (flags & UNOWNED) !== 0; const unowned = (flags & UNOWNED) !== 0;
const dirty = (flags & DIRTY) !== 0; const dirty = (flags & DIRTY) !== 0;
// We skip any effects that are already dirty (but not unowned). Additionally, we also // We skip any effects that are already dirty (but not unowned). Additionally, we also
@ -895,7 +918,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 // are already dirty. Unowned signals might be dirty because they are not captured as part of an
// effect. // effect.
if ((flags & CLEAN) !== 0 || (dirty && unowned)) { 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); schedule_effect(/** @type {import('./types.js').EffectSignal} */ (consumer), false);
} else { } else {
mark_signal_consumers(consumer, MAYBE_DIRTY, force_schedule); mark_signal_consumers(consumer, MAYBE_DIRTY, force_schedule);
@ -916,8 +939,8 @@ export function set_signal_value(signal, value) {
!current_untracking && !current_untracking &&
!ignore_mutation_validation && !ignore_mutation_validation &&
current_consumer !== null && current_consumer !== null &&
is_runes(signal.context) && is_runes(signal.x) &&
(current_consumer.flags & DERIVED) !== 0 (current_consumer.f & DERIVED) !== 0
) { ) {
throw new Error( throw new Error(
"Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " + "Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " +
@ -926,11 +949,11 @@ export function set_signal_value(signal, value) {
); );
} }
if ( if (
(signal.flags & SOURCE) !== 0 && (signal.f & SOURCE) !== 0 &&
!(/** @type {import('./types.js').EqualsFunctions} */ (signal.equals)(value, signal.value)) !(/** @type {import('./types.js').EqualsFunctions} */ (signal.e)(value, signal.v))
) { ) {
const component_context = signal.context; const component_context = signal.x;
signal.value = value; signal.v = value;
// If the current signal is running for the first time, it won't have any // 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 // 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 // has fully executed. So in the case of ensuring it registers the consumer
@ -942,8 +965,8 @@ export function set_signal_value(signal, value) {
if ( if (
is_runes(component_context) && is_runes(component_context) &&
current_effect !== null && current_effect !== null &&
current_effect.consumers === null && current_effect.c === null &&
(current_effect.flags & CLEAN) !== 0 && (current_effect.f & CLEAN) !== 0 &&
current_dependencies !== null && current_dependencies !== null &&
current_dependencies.includes(signal) current_dependencies.includes(signal)
) { ) {
@ -969,22 +992,23 @@ export function set_signal_value(signal, value) {
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @returns {void} * @returns {void}
*/ */
export function destroy_signal(signal) { export function destroy_signal(signal) {
const teardown = /** @type {null | (() => void)} */ (signal.value); const teardown = /** @type {null | (() => void)} */ (signal.v);
const destroy = signal.destroy; const destroy = signal.y;
const flags = signal.f;
destroy_references(signal); destroy_references(signal);
remove_consumer(signal, 0, true); remove_consumer(signal, 0, true);
signal.init = null; signal.i = null;
signal.references = null; signal.r = null;
signal.destroy = null; signal.y = null;
signal.context = null; signal.x = null;
signal.block = null; signal.b = null;
signal.value = /** @type {V} */ (null); signal.v = /** @type {V} */ (null);
signal.dependencies = null; signal.d = null;
signal.consumers = null; signal.c = null;
set_signal_status(signal, DESTROYED); set_signal_status(signal, DESTROYED);
if (destroy !== null) { if (destroy !== null) {
if (is_array(destroy)) { if (is_array(destroy)) {
@ -996,7 +1020,7 @@ export function destroy_signal(signal) {
destroy(); destroy();
} }
} }
if (teardown !== null && (signal.flags & IS_EFFECT) !== 0) { if (teardown !== null && (flags & IS_EFFECT) !== 0) {
teardown(); teardown();
} }
} }
@ -1005,18 +1029,18 @@ export function destroy_signal(signal) {
* @template V * @template V
* @param {() => V} init * @param {() => V} init
* @param {import('./types.js').EqualsFunctions} [equals] * @param {import('./types.js').EqualsFunctions} [equals]
* @returns {import('./types.js').Signal<V>} * @returns {import('./types.js').ComputationSignal<V>}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function derived(init, equals) { export function derived(init, equals) {
const is_unowned = current_effect === null; const is_unowned = current_effect === null;
const flags = is_unowned ? DERIVED | UNOWNED : DERIVED; const flags = is_unowned ? DERIVED | UNOWNED : DERIVED;
const signal = /** @type {import('./types.js').Signal<V>} */ ( const signal = /** @type {import('./types.js').ComputationSignal<V>} */ (
create_signal_object(flags | CLEAN, UNINITIALIZED, current_block) create_computation_signal(flags | CLEAN, UNINITIALIZED, current_block)
); );
signal.init = init; signal.i = init;
signal.context = current_component_context; signal.x = current_component_context;
signal.equals = get_equals_method(equals); signal.e = get_equals_method(equals);
if (!is_unowned) { if (!is_unowned) {
push_reference(/** @type {import('./types.js').EffectSignal} */ (current_effect), signal); push_reference(/** @type {import('./types.js').EffectSignal} */ (current_effect), signal);
} }
@ -1027,13 +1051,13 @@ export function derived(init, equals) {
* @template V * @template V
* @param {V} initial_value * @param {V} initial_value
* @param {import('./types.js').EqualsFunctions<V>} [equals] * @param {import('./types.js').EqualsFunctions<V>} [equals]
* @returns {import('./types.js').Signal<V>} * @returns {import('./types.js').SourceSignal<V>}
*/ */
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function source(initial_value, equals) { 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.x = current_component_context;
source.equals = get_equals_method(equals); source.e = get_equals_method(equals);
return source; return source;
} }
@ -1079,9 +1103,9 @@ export function untrack(fn) {
* @returns {import('./types.js').EffectSignal} * @returns {import('./types.js').EffectSignal}
*/ */
function internal_create_effect(type, init, sync, block, schedule) { 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.i = init;
signal.context = current_component_context; signal.x = current_component_context;
if (schedule) { if (schedule) {
schedule_effect(signal, sync); schedule_effect(signal, sync);
} }
@ -1100,7 +1124,7 @@ export function user_effect(init) {
throw new Error('The Svelte $effect rune can only be used during component initialisation.'); throw new Error('The Svelte $effect rune can only be used during component initialisation.');
} }
const apply_component_effect_heuristics = const apply_component_effect_heuristics =
current_effect.flags & RENDER_EFFECT && current_effect.f & RENDER_EFFECT &&
current_component_context !== null && current_component_context !== null &&
!current_component_context.mounted; !current_component_context.mounted;
const effect = internal_create_effect( const effect = internal_create_effect(
@ -1158,7 +1182,7 @@ export function pre_effect(init) {
'The Svelte $effect.pre rune can only be used during component initialisation.' '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( return internal_create_effect(
PRE_EFFECT, PRE_EFFECT,
() => { () => {
@ -1210,18 +1234,18 @@ export function managed_render_effect(init, block = current_block, sync = true)
/** /**
* @template V * @template V
* @param {import('./types.js').Signal<V>} signal * @param {import('./types.js').ComputationSignal<V>} signal
* @param {() => void} destroy_fn * @param {() => void} destroy_fn
* @returns {void} * @returns {void}
*/ */
export function push_destroy_fn(signal, destroy_fn) { export function push_destroy_fn(signal, destroy_fn) {
let destroy = signal.destroy; let destroy = signal.y;
if (destroy === null) { if (destroy === null) {
signal.destroy = destroy_fn; signal.y = destroy_fn;
} else if (is_array(destroy)) { } else if (is_array(destroy)) {
destroy.push(destroy_fn); destroy.push(destroy_fn);
} else { } else {
signal.destroy = [destroy, destroy_fn]; signal.y = [destroy, destroy_fn];
} }
} }
@ -1232,16 +1256,16 @@ export function push_destroy_fn(signal, destroy_fn) {
* @returns {void} * @returns {void}
*/ */
export function set_signal_status(signal, status) { export function set_signal_status(signal, status) {
const flags = signal.flags; const flags = signal.f;
if ((flags & status) === 0) { if ((flags & status) === 0) {
if ((flags & MAYBE_DIRTY) !== 0) { if ((flags & MAYBE_DIRTY) !== 0) {
signal.flags ^= MAYBE_DIRTY; signal.f ^= MAYBE_DIRTY;
} else if ((flags & CLEAN) !== 0) { } else if ((flags & CLEAN) !== 0) {
signal.flags ^= CLEAN; signal.f ^= CLEAN;
} else if ((flags & DIRTY) !== 0) { } else if ((flags & DIRTY) !== 0) {
signal.flags ^= DIRTY; signal.f ^= DIRTY;
} }
signal.flags ^= status; signal.f ^= status;
} }
} }
@ -1254,7 +1278,7 @@ export function is_signal(val) {
return ( return (
typeof val === 'object' && typeof val === 'object' &&
val !== null && val !== null &&
typeof (/** @type {import('./types.js').Signal<V>} */ (val).flags) === 'number' typeof (/** @type {import('./types.js').Signal<V>} */ (val).f) === 'number'
); );
} }
@ -1300,9 +1324,9 @@ export function prop_source(props_obj, key, default_value, call_default_value) {
if ( if (
is_signal(possible_signal) && is_signal(possible_signal) &&
possible_signal.value === value && possible_signal.v === value &&
update_bound_prop === undefined && update_bound_prop === undefined &&
get_equals_method() === possible_signal.equals get_equals_method() === possible_signal.e
) { ) {
if (should_set_default_value) { if (should_set_default_value) {
set( set(
@ -1344,7 +1368,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) {
return; return;
} }
if (not_equal(immutable, propagating_value, source_signal.value)) { if (not_equal(immutable, propagating_value, source_signal.v)) {
ignore_next2 = true; ignore_next2 = true;
// TODO figure out why we need it this way and the explain in a comment; // 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) // some tests fail is we just do set_signal_value(source_signal, propagating_value)
@ -1366,7 +1390,7 @@ export function prop_source(props_obj, key, default_value, call_default_value) {
return; return;
} }
if (not_equal(immutable, propagating_value, possible_signal.value)) { if (not_equal(immutable, propagating_value, possible_signal.v)) {
ignore_next1 = true; ignore_next1 = true;
untrack(() => update_bound_prop(propagating_value)); untrack(() => update_bound_prop(propagating_value));
} }

@ -253,7 +253,7 @@ function handle_raf(time) {
* @param {HTMLElement} dom * @param {HTMLElement} dom
* @param {() => import('./types.js').TransitionPayload} init * @param {() => import('./types.js').TransitionPayload} init
* @param {'in' | 'out' | 'both' | 'key'} direction * @param {'in' | 'out' | 'both' | 'key'} direction
* @param {import('./types.js').Signal<unknown>} effect * @param {import('./types.js').EffectSignal} effect
* @returns {import('./types.js').Transition} * @returns {import('./types.js').Transition}
*/ */
function create_transition(dom, init, direction, effect) { function create_transition(dom, init, direction, effect) {

@ -46,30 +46,52 @@ export type ComponentContext = {
}; };
}; };
export type Signal<V = unknown> = { // For both SourceSignal and ComputationSignal, we use signal character property string.
/** The block associated with this effect/computed */ // This now only reduces code-size and parsing, but it also improves the performance of the JIT compiler.
block: null | Block; // It's likely not to have any real wins wwhen the JIT is disabled. Lastly, we keep two shapes rather than
/** Signals that read from the current signal */ // a single monomorphic shape to improve the memory usage. Source signals don't need the same shape as they
consumers: null | Signal[]; // simply don't do as much as computations (effects and derived signals). Thus we can improve the memory
/** The associated component if this signal is an effect/computed */ // profile at the slight cost of some runtime performance.
context: null | ComponentContext;
/** Signals that this signal reads from */ export type SourceSignal<V = unknown> = {
dependencies: null | Signal[]; /** consumers: Signals that read from the current signal */
/** Thing(s) that need destroying */ c: null | ComputationSignal[];
destroy: null | (() => void) | Array<() => void>; /** context: The associated component if this signal is an effect/computed */
/** For value equality */ x: null | ComponentContext;
equals: null | EqualsFunctions; /** 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<V = unknown> = {
/** 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<V>[];
/** 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 */ /** The types that the signal represent, as a bitwise value */
flags: SignalFlags; f: SignalFlags;
/** The function that we invoke for effects and computeds */ /** init: The function that we invoke for effects and computeds */
init: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void)); i: null | (() => V) | (() => void | (() => void)) | ((b: Block) => void | (() => void));
/** Anything that a signal owns */ /** references: Anything that a signal owns */
references: null | Signal[]; r: null | ComputationSignal[];
/** The latest value for this signal, doubles as the teardown for effects */ /** value: The latest value for this signal, doubles as the teardown for effects */
value: V; v: V;
}; };
export type EffectSignal = Signal<null | (() => void)>; export type Signal<V = unknown> = SourceSignal<V> | ComputationSignal<V>;
export type EffectSignal = ComputationSignal<null | (() => void)>;
export type MaybeSignal<T = unknown> = T | Signal<T>; export type MaybeSignal<T = unknown> = T | Signal<T>;
@ -92,7 +114,7 @@ export type BlockType =
export type TemplateNode = Text | Element | Comment; export type TemplateNode = Text | Element | Comment;
export type Transition = { export type Transition = {
effect: Signal; effect: EffectSignal;
payload: null | TransitionPayload; payload: null | TransitionPayload;
init: (from?: DOMRect) => TransitionPayload; init: (from?: DOMRect) => TransitionPayload;
finished: (fn: () => void) => void; finished: (fn: () => void) => void;
@ -106,7 +128,7 @@ export type Transition = {
export type RootBlock = { export type RootBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
container: Node; container: Node;
intro: boolean; intro: boolean;
parent: null; parent: null;
@ -117,7 +139,7 @@ export type RootBlock = {
export type IfBlock = { export type IfBlock = {
current: boolean; current: boolean;
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof IF_BLOCK; type: typeof IF_BLOCK;
@ -125,7 +147,7 @@ export type IfBlock = {
export type KeyBlock = { export type KeyBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof KEY_BLOCK; type: typeof KEY_BLOCK;
@ -133,7 +155,7 @@ export type KeyBlock = {
export type HeadBlock = { export type HeadBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof HEAD_BLOCK; type: typeof HEAD_BLOCK;
@ -141,7 +163,7 @@ export type HeadBlock = {
export type DynamicElementBlock = { export type DynamicElementBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof DYNAMIC_ELEMENT_BLOCK; type: typeof DYNAMIC_ELEMENT_BLOCK;
@ -149,7 +171,7 @@ export type DynamicElementBlock = {
export type DynamicComponentBlock = { export type DynamicComponentBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
type: typeof DYNAMIC_COMPONENT_BLOCK; type: typeof DYNAMIC_COMPONENT_BLOCK;
@ -157,7 +179,7 @@ export type DynamicComponentBlock = {
export type AwaitBlock = { export type AwaitBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
pending: boolean; pending: boolean;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
@ -169,7 +191,7 @@ export type EachBlock = {
flags: number; flags: number;
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
items: EachItemBlock[]; items: EachItemBlock[];
effect: null | Signal; effect: null | ComputationSignal;
parent: Block; parent: Block;
transition: null | ((transition: Transition) => void); transition: null | ((transition: Transition) => void);
transitions: Array<EachItemBlock>; transitions: Array<EachItemBlock>;
@ -178,7 +200,7 @@ export type EachBlock = {
export type EachItemBlock = { export type EachItemBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
effect: null | Signal; effect: null | ComputationSignal;
item: any | Signal<any>; item: any | Signal<any>;
index: number | Signal<number>; index: number | Signal<number>;
key: unknown; key: unknown;
@ -191,7 +213,7 @@ export type EachItemBlock = {
export type SnippetBlock = { export type SnippetBlock = {
dom: null | TemplateNode | Array<TemplateNode>; dom: null | TemplateNode | Array<TemplateNode>;
parent: Block; parent: Block;
effect: null | Signal; effect: null | ComputationSignal;
transition: null; transition: null;
type: typeof SNIPPET_BLOCK; type: typeof SNIPPET_BLOCK;
}; };

@ -20,8 +20,8 @@ export default function Class_state_field_constructor_assignment($$anchor, $$pro
#b = $.source(); #b = $.source();
constructor() { constructor() {
this.#a.value = 1; this.#a.v = 1;
this.#b.value = 2; this.#b.v = 2;
} }
} }

Loading…
Cancel
Save