blockless
Rich Harris 2 years ago
parent 4abc436fab
commit 8d383aed7e

@ -15,11 +15,11 @@ export function derived(fn) {
/** @type {import('#client').Derived<V>} */ /** @type {import('#client').Derived<V>} */
const signal = { const signal = {
c: null, consumers: null,
d: null, d: null,
e: default_equals, eq: default_equals,
f: flags, f: flags,
i: fn, fn: fn,
r: null, r: null,
// @ts-expect-error // @ts-expect-error
v: UNINITIALIZED, v: UNINITIALIZED,
@ -51,6 +51,6 @@ export function derived(fn) {
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function derived_safe_equal(fn) { export function derived_safe_equal(fn) {
const signal = derived(fn); const signal = derived(fn);
signal.e = safe_equal; signal.eq = safe_equal;
return signal; return signal;
} }

@ -31,7 +31,7 @@ function create_effect(type, fn, sync, schedule) {
d: null, d: null,
f: type | DIRTY, f: type | DIRTY,
l: 0, l: 0,
i: fn, fn: fn,
r: null, r: null,
v: null, v: null,
ctx: current_component_context, ctx: current_component_context,

@ -12,15 +12,10 @@ import { CLEAN, SOURCE } from '../constants.js';
export function source(value) { export function source(value) {
/** @type {import('#client').Source<V>} */ /** @type {import('#client').Source<V>} */
const source = { const source = {
// consumers
c: null,
// equals
e: default_equals,
// flags
f: SOURCE | CLEAN, f: SOURCE | CLEAN,
// value
v: value, v: value,
// write version eq: default_equals,
consumers: null,
w: 0 w: 0
}; };
@ -39,7 +34,7 @@ export function source(value) {
/*#__NO_SIDE_EFFECTS__*/ /*#__NO_SIDE_EFFECTS__*/
export function mutable_source(initial_value) { export function mutable_source(initial_value) {
const s = source(initial_value); const s = source(initial_value);
s.e = safe_equal; s.eq = safe_equal;
// bind the signal to the component context, in case we need to // bind the signal to the component context, in case we need to
// track updates to trigger beforeUpdate/afterUpdate callbacks // track updates to trigger beforeUpdate/afterUpdate callbacks

@ -12,9 +12,9 @@ export type EffectType = typeof EFFECT | typeof PRE_EFFECT | typeof RENDER_EFFEC
export interface Source<V = unknown> { export interface Source<V = unknown> {
/** consumers: Signals that read from the current signal */ /** consumers: Signals that read from the current signal */
c: null | Reaction[]; consumers: null | Reaction[];
/** equals: For value equality */ /** equals: For value equality */
e: EqualsFunctions; eq: EqualsFunctions;
/** flags: The types that the signal represent, as a bitwise value */ /** flags: The types that the signal represent, as a bitwise value */
f: SignalFlags; f: SignalFlags;
/** value: The latest value for this signal */ /** value: The latest value for this signal */
@ -27,23 +27,13 @@ export interface SourceDebug<V = unknown> extends Source<V> {
inspect: Set<Function>; inspect: Set<Function>;
} }
export interface Derived<V = unknown> { export interface Derived<V = unknown> extends Source<V> {
/** consumers: Signals that read from the current signal */
c: null | Reaction[];
/** dependencies: Signals that this signal reads from */ /** dependencies: Signals that this signal reads from */
d: null | ValueSignal[]; d: null | ValueSignal[];
/** equals: For value equality */
e: EqualsFunctions;
/** The types that the signal represent, as a bitwise value */
f: SignalFlags;
/** init: The function that we invoke for effects and computeds */ /** init: The function that we invoke for effects and computeds */
i: () => V; fn: () => V;
/** references: Anything that a signal owns */ /** references: Anything that a signal owns */
r: null | Reaction[]; r: null | Reaction[];
/** value: The latest value for this signal */
v: V;
/** write version: used for unowned signals to track if their depdendencies are dirty or not **/
w: number;
} }
export interface DerivedDebug<V = unknown> extends Derived<V> { export interface DerivedDebug<V = unknown> extends Derived<V> {
@ -60,7 +50,7 @@ export interface Effect {
/** The types that the signal represent, as a bitwise value */ /** The types that the signal represent, as a bitwise value */
f: SignalFlags; f: SignalFlags;
/** init: The function that we invoke for effects and computeds */ /** init: The function that we invoke for effects and computeds */
i: null | (() => void | (() => void)); fn: null | (() => void | (() => void));
/** deriveds belonging to this effect */ /** deriveds belonging to this effect */
r: null | Derived[]; r: null | Derived[];
/** teardown */ /** teardown */

@ -67,8 +67,8 @@ import {
import { run } from '../common.js'; import { run } from '../common.js';
import { bind_transition } from './transitions.js'; import { bind_transition } from './transitions.js';
import { mutable_source, source } from './reactivity/sources.js'; import { mutable_source, source } from './reactivity/sources.js';
import { safe_equal, safe_not_equal } from './reactivity/equality.js'; import { safe_not_equal } from './reactivity/equality.js';
import { derived } from './reactivity/deriveds.js'; import { derived, derived_safe_equal } from './reactivity/deriveds.js';
/** @type {Set<string>} */ /** @type {Set<string>} */
const all_registered_events = new Set(); const all_registered_events = new Set();
@ -2448,7 +2448,7 @@ export function prop(props, key, flags, initial) {
// The derived returns the current value. The underlying mutable // The derived returns the current value. The underlying mutable
// source is written to from various places to persist this value. // source is written to from various places to persist this value.
var inner_current_value = mutable_source(prop_value); var inner_current_value = mutable_source(prop_value);
var current_value = derived(() => { var current_value = (immutable ? derived : derived_safe_equal)(() => {
var parent_value = getter(); var parent_value = getter();
var child_value = get(inner_current_value); var child_value = get(inner_current_value);
@ -2462,8 +2462,6 @@ export function prop(props, key, flags, initial) {
return (inner_current_value.v = parent_value); return (inner_current_value.v = parent_value);
}); });
if (!immutable) current_value.e = safe_equal;
return function (/** @type {V} */ value, mutation = false) { return function (/** @type {V} */ value, mutation = false) {
var current = get(current_value); var current = get(current_value);

@ -212,8 +212,8 @@ function is_signal_dirty(signal) {
* @param {import('#client').Reaction} signal * @param {import('#client').Reaction} signal
* @returns {V} * @returns {V}
*/ */
function execute_signal_fn(signal) { function execute_reaction(signal) {
const init = signal.i; const init = signal.fn;
const flags = signal.f; const flags = signal.f;
const previous_dependencies = current_dependencies; const previous_dependencies = current_dependencies;
const previous_dependencies_index = current_dependencies_index; const previous_dependencies_index = current_dependencies_index;
@ -273,10 +273,10 @@ function execute_signal_fn(signal) {
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];
const consumers = dependency.c; const consumers = dependency.consumers;
if (consumers === null) { if (consumers === null) {
dependency.c = [signal]; dependency.consumers = [signal];
} else if (consumers[consumers.length - 1] !== signal) { } else if (consumers[consumers.length - 1] !== signal) {
// TODO: should this be: // TODO: should this be:
// //
@ -308,14 +308,14 @@ function execute_signal_fn(signal) {
* @returns {void} * @returns {void}
*/ */
function remove_consumer(signal, dependency) { function remove_consumer(signal, dependency) {
const consumers = dependency.c; const consumers = dependency.consumers;
let consumers_length = 0; let consumers_length = 0;
if (consumers !== null) { if (consumers !== null) {
consumers_length = consumers.length - 1; consumers_length = consumers.length - 1;
const index = consumers.indexOf(signal); const index = consumers.indexOf(signal);
if (index !== -1) { if (index !== -1) {
if (consumers_length === 0) { if (consumers_length === 0) {
dependency.c = null; dependency.consumers = null;
} else { } else {
// Swap with last element and then remove. // Swap with last element and then remove.
consumers[index] = consumers[consumers_length]; consumers[index] = consumers[consumers_length];
@ -394,7 +394,7 @@ export function execute_effect(effect) {
current_component_context = component_context; current_component_context = component_context;
const possible_teardown = execute_signal_fn(effect); const possible_teardown = execute_reaction(effect);
if (typeof possible_teardown === 'function') { if (typeof possible_teardown === 'function') {
effect.v = possible_teardown; effect.v = possible_teardown;
@ -660,15 +660,17 @@ export async function tick() {
function update_derived(signal, force_schedule) { function update_derived(signal, force_schedule) {
const previous_updating_derived = updating_derived; const previous_updating_derived = updating_derived;
updating_derived = true; updating_derived = true;
const value = execute_signal_fn(signal); const value = execute_reaction(signal);
updating_derived = previous_updating_derived; updating_derived = previous_updating_derived;
const status = const status =
(current_skip_consumer || (signal.f & UNOWNED) !== 0) && signal.d !== null (current_skip_consumer || (signal.f & UNOWNED) !== 0) && signal.d !== null
? MAYBE_DIRTY ? MAYBE_DIRTY
: CLEAN; : CLEAN;
set_signal_status(signal, status); set_signal_status(signal, status);
const equals = /** @type {import('#client').EqualsFunctions} */ (signal.e);
if (!equals(value, signal.v)) { if (!signal.eq(value, signal.v)) {
signal.v = value; signal.v = value;
mark_signal_consumers(signal, DIRTY, force_schedule); mark_signal_consumers(signal, DIRTY, force_schedule);
@ -772,13 +774,13 @@ export function set(signal, value) {
: '') : '')
); );
} }
if (
(signal.f & SOURCE) !== 0 && if (!signal.eq(value, signal.v)) {
!(/** @type {import('#client').EqualsFunctions} */ (signal.e)(value, signal.v))
) {
signal.v = value; signal.v = value;
// Increment write version so that unowned signals can properly track dirtyness // Increment write version so that unowned signals can properly track dirtyness
signal.w++; signal.w++;
// 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
@ -807,6 +809,7 @@ export function set(signal, value) {
} }
} }
} }
mark_signal_consumers(signal, DIRTY, true); mark_signal_consumers(signal, DIRTY, true);
// @ts-expect-error // @ts-expect-error
@ -881,7 +884,7 @@ export function mutate(source, value) {
*/ */
function mark_signal_consumers(signal, to_status, force_schedule) { function mark_signal_consumers(signal, to_status, force_schedule) {
const runes = is_runes(null); const runes = is_runes(null);
const consumers = signal.c; const consumers = signal.consumers;
if (consumers !== null) { if (consumers !== null) {
const length = consumers.length; const length = consumers.length;

@ -192,13 +192,13 @@ describe('signals', () => {
return () => { return () => {
$.flushSync(() => $.set(count, 1)); $.flushSync(() => $.set(count, 1));
// Ensure we're not leaking consumers // Ensure we're not leaking consumers
assert.deepEqual(count.c?.length, 1); assert.deepEqual(count.consumers?.length, 1);
$.flushSync(() => $.set(count, 2)); $.flushSync(() => $.set(count, 2));
// Ensure we're not leaking consumers // Ensure we're not leaking consumers
assert.deepEqual(count.c?.length, 1); assert.deepEqual(count.consumers?.length, 1);
$.flushSync(() => $.set(count, 3)); $.flushSync(() => $.set(count, 3));
// Ensure we're not leaking consumers // Ensure we're not leaking consumers
assert.deepEqual(count.c?.length, 1); assert.deepEqual(count.consumers?.length, 1);
assert.deepEqual(log, [0, 1, 2, 3]); assert.deepEqual(log, [0, 1, 2, 3]);
}; };
}); });
@ -258,11 +258,11 @@ describe('signals', () => {
$.flushSync(() => $.set(count, 4)); $.flushSync(() => $.set(count, 4));
$.flushSync(() => $.set(count, 0)); $.flushSync(() => $.set(count, 0));
// Ensure we're not leaking consumers // Ensure we're not leaking consumers
assert.deepEqual(count.c?.length, 1); assert.deepEqual(count.consumers?.length, 1);
assert.deepEqual(log, [0, 2, 'limit', 0]); assert.deepEqual(log, [0, 2, 'limit', 0]);
$.destroy_signal(effect); $.destroy_signal(effect);
// Ensure we're not leaking consumers // Ensure we're not leaking consumers
assert.deepEqual(count.c, null); assert.deepEqual(count.consumers, null);
}; };
}); });

Loading…
Cancel
Save