blockless
Rich Harris 2 years ago
parent 20b7b54b22
commit e6b8022187

@ -15,14 +15,13 @@ import {
} from '../../hydration.js';
import { empty, map_get, map_set } from '../../operations.js';
import { insert, remove } from '../../reconciler.js';
import { set } from '../../runtime.js';
import {
destroy_effect,
pause_effect,
render_effect,
resume_effect
} from '../../reactivity/effects.js';
import { source, mutable_source } from '../../reactivity/sources.js';
import { source, mutable_source, set } from '../../reactivity/sources.js';
import { is_array } from '../../utils.js';
import { BRANCH_EFFECT } from '../../constants.js';

@ -1,7 +1,6 @@
import { DEV } from 'esm-env';
import {
get,
set,
updating_derived,
batch_inspect,
current_component_context,
@ -19,7 +18,7 @@ import {
object_prototype
} from './utils.js';
import { add_owner, check_ownership, strip_owner } from './dev/ownership.js';
import { mutable_source, source } from './reactivity/sources.js';
import { mutable_source, source, set } from './reactivity/sources.js';
import { STATE_SYMBOL, UNINITIALIZED } from './constants.js';
/**

@ -1,7 +1,26 @@
import { DEV } from 'esm-env';
import { current_component_context } from '../runtime.js';
import {
current_component_context,
current_consumer,
current_dependencies,
current_effect,
current_untracked_writes,
current_untracking,
flushSync,
get,
ignore_mutation_validation,
is_batching_effect,
is_runes,
last_inspected_signal,
mark_signal_consumers,
schedule_effect,
set_current_untracked_writes,
set_last_inspected_signal,
set_signal_status,
untrack
} from '../runtime.js';
import { default_equals, safe_equal } from './equality.js';
import { CLEAN, SOURCE } from '../constants.js';
import { CLEAN, DERIVED, DIRTY, MANAGED, SOURCE } from '../constants.js';
/**
* @template V
@ -44,3 +63,100 @@ export function mutable_source(initial_value) {
return s;
}
/**
* @template V
* @param {import('#client').Source<V>} signal
* @param {V} value
* @returns {V}
*/
export function set(signal, value) {
if (
!current_untracking &&
!ignore_mutation_validation &&
current_consumer !== null &&
is_runes(null) &&
(current_consumer.f & DERIVED) !== 0
) {
throw new Error(
'ERR_SVELTE_UNSAFE_MUTATION' +
(DEV
? ": Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " +
'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' +
'to be reactive do not use the "$state" rune for that declaration.'
: '')
);
}
if (!signal.eq(value, signal.v)) {
signal.v = value;
// Increment write version so that unowned signals can properly track dirtyness
signal.w++;
// 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
// properly for itself, we need to ensure the current effect actually gets
// scheduled. i.e:
//
// $effect(() => x++)
//
// We additionally want to skip this logic for when ignore_mutation_validation is
// true, as stores write to source signal on initialization.
if (
is_runes(null) &&
!ignore_mutation_validation &&
current_effect !== null &&
(current_effect.f & CLEAN) !== 0 &&
(current_effect.f & MANAGED) === 0
) {
if (current_dependencies !== null && current_dependencies.includes(signal)) {
set_signal_status(current_effect, DIRTY);
schedule_effect(current_effect, false);
} else {
if (current_untracked_writes === null) {
set_current_untracked_writes([signal]);
} else {
current_untracked_writes.push(signal);
}
}
}
mark_signal_consumers(signal, DIRTY, true);
// @ts-expect-error
if (DEV && signal.inspect) {
if (is_batching_effect) {
set_last_inspected_signal(/** @type {import('#client').SourceDebug} */ (signal));
} else {
for (const fn of /** @type {import('#client').SourceDebug} */ (signal).inspect) fn();
}
}
}
return value;
}
/**
* @template V
* @param {import('#client').Source<V>} signal
* @param {V} value
* @returns {void}
*/
export function set_sync(signal, value) {
flushSync(() => set(signal, value));
}
/**
* @template V
* @param {import('#client').Source<V>} source
* @param {V} value
*/
export function mutate(source, value) {
set(
source,
untrack(() => get(source))
);
return value;
}

@ -1,9 +1,9 @@
import { subscribe_to_store } from '../../../store/utils.js';
import { noop } from '../../common.js';
import { UNINITIALIZED } from '../constants.js';
import { get, set, set_ignore_mutation_validation, untrack } from '../runtime.js';
import { get, set_ignore_mutation_validation, untrack } from '../runtime.js';
import { user_effect } from './effects.js';
import { mutable_source } from './sources.js';
import { mutable_source, set } from './sources.js';
/**
* Gets the current value of a store. If the store isn't subscribed to yet, it will create a proxy

@ -35,7 +35,6 @@ import {
current_component_context,
deep_read,
get,
set,
is_signals_recorded,
inspect_fn,
current_effect
@ -66,7 +65,7 @@ import {
} from './utils.js';
import { run } from '../common.js';
import { bind_transition } from './transitions.js';
import { mutable_source, source } from './reactivity/sources.js';
import { mutable_source, source, set } from './reactivity/sources.js';
import { safe_not_equal } from './reactivity/equality.js';
import { derived, derived_safe_equal } from './reactivity/deriveds.js';

@ -28,6 +28,7 @@ import {
BRANCH_EFFECT
} from './constants.js';
import { flush_tasks } from './dom/task.js';
import { mutate, set } from './reactivity/sources.js';
const IS_EFFECT = EFFECT | PRE_EFFECT | RENDER_EFFECT;
@ -66,20 +67,33 @@ export function set_current_effect(effect) {
}
/** @type {null | import('#client').ValueSignal[]} */
let current_dependencies = null;
export let current_dependencies = null;
let current_dependencies_index = 0;
/**
* Tracks writes that the effect it's executed in doesn't listen to yet,
* so that the dependency can be added to the effect later on if it then reads it
* @type {null | import('#client').Source[]}
*/
let current_untracked_writes = null;
export let current_untracked_writes = null;
/** @param {null | import('#client').Source[]} writes */
export function set_current_untracked_writes(writes) {
current_untracked_writes = writes;
}
/** @type {null | import('#client').SourceDebug} */
let last_inspected_signal = null;
export let last_inspected_signal = null;
/** @param {import('#client').SourceDebug} signal */
export function set_last_inspected_signal(signal) {
last_inspected_signal = signal;
}
/** If `true`, `get`ting the signal should not register it as a dependency */
export let current_untracking = false;
/** Exists to opt out of the mutation validation for stores which may be set for the first time during a derivation */
let ignore_mutation_validation = false;
export let ignore_mutation_validation = false;
/** @param {boolean} value */
export function set_ignore_mutation_validation(value) {
ignore_mutation_validation = value;
@ -113,7 +127,7 @@ export let updating_derived = false;
* @param {null | import('#client').ComponentContext} context
* @returns {boolean}
*/
function is_runes(context) {
export function is_runes(context) {
const component_context = context || current_component_context;
return component_context !== null && component_context.r;
}
@ -751,90 +765,6 @@ export function get(signal) {
return signal.v;
}
/**
* @template V
* @param {import('#client').Source<V>} signal
* @param {V} value
* @returns {V}
*/
export function set(signal, value) {
if (
!current_untracking &&
!ignore_mutation_validation &&
current_consumer !== null &&
is_runes(null) &&
(current_consumer.f & DERIVED) !== 0
) {
throw new Error(
'ERR_SVELTE_UNSAFE_MUTATION' +
(DEV
? ": Unsafe mutations during Svelte's render or derived phase are not permitted in runes mode. " +
'This can lead to unexpected errors and possibly cause infinite loops.\n\nIf this mutation is not meant ' +
'to be reactive do not use the "$state" rune for that declaration.'
: '')
);
}
if (!signal.eq(value, signal.v)) {
signal.v = value;
// Increment write version so that unowned signals can properly track dirtyness
signal.w++;
// 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
// properly for itself, we need to ensure the current effect actually gets
// scheduled. i.e:
//
// $effect(() => x++)
//
// We additionally want to skip this logic for when ignore_mutation_validation is
// true, as stores write to source signal on initialization.
if (
is_runes(null) &&
!ignore_mutation_validation &&
current_effect !== null &&
(current_effect.f & CLEAN) !== 0 &&
(current_effect.f & MANAGED) === 0
) {
if (current_dependencies !== null && current_dependencies.includes(signal)) {
set_signal_status(current_effect, DIRTY);
schedule_effect(current_effect, false);
} else {
if (current_untracked_writes === null) {
current_untracked_writes = [signal];
} else {
current_untracked_writes.push(signal);
}
}
}
mark_signal_consumers(signal, DIRTY, true);
// @ts-expect-error
if (DEV && signal.inspect) {
if (is_batching_effect) {
last_inspected_signal = /** @type {import('#client').SourceDebug} */ (signal);
} else {
for (const fn of /** @type {import('#client').SourceDebug} */ (signal).inspect) fn();
}
}
}
return value;
}
/**
* @template V
* @param {import('#client').Source<V>} signal
* @param {V} value
* @returns {void}
*/
export function set_sync(signal, value) {
flushSync(() => set(signal, value));
}
/**
* Invokes a function and captures all signals that are read during the invocation,
* then invalidates them.
@ -863,26 +793,13 @@ export function invalidate_inner_signals(fn) {
}
}
/**
* @template V
* @param {import('#client').Source<V>} source
* @param {V} value
*/
export function mutate(source, value) {
set(
source,
untrack(() => get(source))
);
return value;
}
/**
* @param {import('#client').ValueSignal} signal
* @param {number} to_status
* @param {boolean} force_schedule
* @returns {void}
*/
function mark_signal_consumers(signal, to_status, force_schedule) {
export function mark_signal_consumers(signal, to_status, force_schedule) {
const runes = is_runes(null);
const consumers = signal.consumers;
@ -974,7 +891,7 @@ const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
* @param {number} status
* @returns {void}
*/
function set_signal_status(signal, status) {
export function set_signal_status(signal, status) {
signal.f = (signal.f & STATUS_MASK) | status;
}

@ -1,7 +1,5 @@
export {
get,
set,
set_sync,
invalidate_inner_signals,
flushSync,
tick,
@ -10,7 +8,6 @@ export {
update_prop,
update_pre,
update_pre_prop,
mutate,
value_or_fallback,
exclude_from_object,
pop,

@ -1,5 +1,5 @@
import { source } from '../internal/client/reactivity/sources.js';
import { get, set } from '../internal/client/runtime.js';
import { source, set } from '../internal/client/reactivity/sources.js';
import { get } from '../internal/client/runtime.js';
/** @type {Array<keyof Date>} */
const read = [

@ -5,6 +5,7 @@ import { source } from '../../src/internal/client/reactivity/sources';
import type { Derived } from '../../src/internal/client/reactivity/types';
import { proxy } from '../../src/internal/client/proxy';
import { derived } from '../../src/internal/client/reactivity/deriveds';
import { set } from '../../src/internal/client/reactivity/sources';
/**
* @param runes runes mode
@ -46,8 +47,8 @@ describe('signals', () => {
});
return () => {
$.flushSync(() => $.set(count, 1));
$.flushSync(() => $.set(count, 2));
$.flushSync(() => set(count, 1));
$.flushSync(() => set(count, 2));
assert.deepEqual(log, ['0:0', '1:2', '2:4']);
};
@ -67,8 +68,8 @@ describe('signals', () => {
});
return () => {
$.flushSync(() => $.set(count, 1));
$.flushSync(() => $.set(count, 2));
$.flushSync(() => set(count, 1));
$.flushSync(() => set(count, 2));
assert.deepEqual(log, ['A:0:0', 'B:0', 'A:1:2', 'B:2', 'A:2:4', 'B:4']);
};
@ -88,8 +89,8 @@ describe('signals', () => {
});
return () => {
$.flushSync(() => $.set(count, 1));
$.flushSync(() => $.set(count, 2));
$.flushSync(() => set(count, 1));
$.flushSync(() => set(count, 2));
assert.deepEqual(log, ['A:0', 'B:0:0', 'A:2', 'B:1:2', 'A:4', 'B:2:4']);
};
@ -106,8 +107,8 @@ describe('signals', () => {
});
return () => {
$.flushSync(() => $.set(count, 1));
$.flushSync(() => $.set(count, 2));
$.flushSync(() => set(count, 1));
$.flushSync(() => set(count, 2));
assert.deepEqual(log, [0, 2, 4]);
};
@ -125,8 +126,8 @@ describe('signals', () => {
});
return () => {
$.flushSync(() => $.set(count, 1));
$.flushSync(() => $.set(count, 2));
$.flushSync(() => set(count, 1));
$.flushSync(() => set(count, 2));
assert.deepEqual(log, [0, 4, 8]);
};
@ -162,12 +163,12 @@ describe('signals', () => {
let i = 2;
while (--i) {
res.length = 0;
$.set(B, 1);
$.set(A, 1 + i * 2);
set(B, 1);
set(A, 1 + i * 2);
$.flushSync();
$.set(A, 2 + i * 2);
$.set(B, 2);
set(A, 2 + i * 2);
set(B, 2);
$.flushSync();
assert.equal(res.length, 4);
@ -190,13 +191,13 @@ describe('signals', () => {
});
return () => {
$.flushSync(() => $.set(count, 1));
$.flushSync(() => set(count, 1));
// Ensure we're not leaking consumers
assert.deepEqual(count.consumers?.length, 1);
$.flushSync(() => $.set(count, 2));
$.flushSync(() => set(count, 2));
// Ensure we're not leaking consumers
assert.deepEqual(count.consumers?.length, 1);
$.flushSync(() => $.set(count, 3));
$.flushSync(() => set(count, 3));
// Ensure we're not leaking consumers
assert.deepEqual(count.consumers?.length, 1);
assert.deepEqual(log, [0, 1, 2, 3]);
@ -219,11 +220,11 @@ describe('signals', () => {
$.get(c);
$.flushSync(() => $.set(a, 1));
$.flushSync(() => set(a, 1));
$.get(c);
$.flushSync(() => $.set(b, 1));
$.flushSync(() => set(b, 1));
$.get(c);
@ -252,11 +253,11 @@ describe('signals', () => {
});
return () => {
$.flushSync(() => $.set(count, 1));
$.flushSync(() => $.set(count, 2));
$.flushSync(() => $.set(count, 3));
$.flushSync(() => $.set(count, 4));
$.flushSync(() => $.set(count, 0));
$.flushSync(() => set(count, 1));
$.flushSync(() => set(count, 2));
$.flushSync(() => set(count, 3));
$.flushSync(() => set(count, 4));
$.flushSync(() => set(count, 0));
// Ensure we're not leaking consumers
assert.deepEqual(count.consumers?.length, 1);
assert.deepEqual(log, [0, 2, 'limit', 0]);
@ -314,7 +315,7 @@ describe('signals', () => {
const value = source({ count: 0 });
user_effect(() => {
$.set(value, { count: 0 });
set(value, { count: 0 });
$.get(value);
});

Loading…
Cancel
Save