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

@ -24,7 +24,6 @@ export function derived(fn) {
// @ts-expect-error // @ts-expect-error
v: UNINITIALIZED, v: UNINITIALIZED,
w: 0, w: 0,
x: null,
parent: current_effect parent: current_effect
}; };

@ -13,7 +13,6 @@ import {
RENDER_EFFECT, RENDER_EFFECT,
EFFECT, EFFECT,
PRE_EFFECT, PRE_EFFECT,
DERIVED,
INERT, INERT,
ROOT_EFFECT, ROOT_EFFECT,
DESTROYED DESTROYED
@ -29,16 +28,13 @@ import {
function create_effect(type, fn, sync, schedule) { function create_effect(type, fn, sync, schedule) {
/** @type {import('#client').Effect} */ /** @type {import('#client').Effect} */
const signal = { const signal = {
c: null,
d: null, d: null,
e: null,
f: type | DIRTY, f: type | DIRTY,
l: 0, l: 0,
i: fn, i: fn,
r: null, r: null,
v: null, v: null,
w: 0, ctx: current_component_context,
x: current_component_context,
y: null, y: null,
in: null, in: null,
out: null, out: null,
@ -189,10 +185,9 @@ export function invalidate_effect(fn) {
*/ */
export function render_effect(fn, managed = false, sync = true) { export function render_effect(fn, managed = false, sync = true) {
let flags = RENDER_EFFECT; let flags = RENDER_EFFECT;
if (managed) { if (managed) flags |= MANAGED;
flags |= MANAGED;
} return create_effect(flags, fn, sync, true);
return create_effect(flags, /** @type {any} */ (fn), sync, true);
} }
/** /**
@ -233,7 +228,7 @@ function pause_children(effect, transitions, local) {
if ((effect.f & INERT) !== 0) return; if ((effect.f & INERT) !== 0) return;
effect.f |= INERT; effect.f |= INERT;
if ((effect.f & DERIVED) === 0 && typeof effect.v === 'function') { if (typeof effect.v === 'function') {
effect.v(); effect.v();
} }
@ -287,8 +282,8 @@ export function resume_effect(effect) {
* @param {boolean} local * @param {boolean} local
*/ */
function resume_children(effect, local) { function resume_children(effect, local) {
if ((effect.f & DERIVED) === 0 && (effect.f & MANAGED) === 0) { if ((effect.f & MANAGED) === 0) {
execute_effect(/** @type {import('#client').Effect} */ (effect)); execute_effect(effect);
} }
if (effect.children) { if (effect.children) {

@ -14,7 +14,7 @@ export interface Source<V = unknown> {
/** consumers: Signals that read from the current signal */ /** consumers: Signals that read from the current signal */
c: null | Reaction[]; c: null | Reaction[];
/** equals: For value equality */ /** equals: For value equality */
e: null | EqualsFunctions; e: 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 */
@ -30,12 +30,10 @@ export interface SourceDebug<V = unknown> extends Source<V> {
export interface Derived<V = unknown> { export interface Derived<V = unknown> {
/** consumers: Signals that read from the current signal */ /** consumers: Signals that read from the current signal */
c: null | Reaction[]; c: null | Reaction[];
/** context: The associated component if this signal is an effect/computed */
x: null | ComponentContext;
/** dependencies: Signals that this signal reads from */ /** dependencies: Signals that this signal reads from */
d: null | ValueSignal[]; d: null | ValueSignal[];
/** equals: For value equality */ /** equals: For value equality */
e: null | EqualsFunctions; e: EqualsFunctions;
/** 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 */
@ -53,16 +51,12 @@ export interface DerivedDebug<V = unknown> extends Derived<V> {
} }
export interface Effect { export interface Effect {
/** consumers: Signals that read from the current signal */
c: null | Reaction[];
/** context: The associated component if this signal is an effect/computed */ /** context: The associated component if this signal is an effect/computed */
x: null | ComponentContext; ctx: null | ComponentContext;
/** dependencies: Signals that this signal reads from */ /** dependencies: Signals that this signal reads from */
d: null | ValueSignal[]; d: null | ValueSignal[];
/** destroy: Thing(s) that need destroying */ /** destroy: Thing(s) that need destroying */
y: null | (() => void) | Array<() => void>; y: null | (() => 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 */
f: SignalFlags; f: SignalFlags;
/** init: The function that we invoke for effects and computeds */ /** init: The function that we invoke for effects and computeds */
@ -73,8 +67,6 @@ export interface Effect {
v: null | (() => void); v: null | (() => void);
/** level: the depth from the root signal, used for ordering render/pre-effects topologically **/ /** level: the depth from the root signal, used for ordering render/pre-effects topologically **/
l: number; l: number;
/** write version: used for unowned signals to track if their depdendencies are dirty or not **/
w: number;
/** in transitions */ /** in transitions */
in: null | Transition[]; in: null | Transition[];
/** out transitions */ /** out transitions */
@ -93,4 +85,4 @@ export type ValueSignal<V = unknown> = Source<V> | Derived<V>;
export type ValueSignalDebug<V = unknown> = SourceDebug<V> | DerivedDebug<V>; export type ValueSignalDebug<V = unknown> = SourceDebug<V> | DerivedDebug<V>;
export type Signal<V = unknown> = Source<V> | Reaction; export type Signal = Source | Derived | Effect;

@ -152,29 +152,34 @@ export function batch_inspect(target, prop, receiver) {
/** /**
* @template V * @template V
* @param {import('#client').ValueSignal<V>} signal * @param {import('#client').ValueSignal<V> | import('#client').Effect} signal
* @returns {boolean} * @returns {boolean}
*/ */
function is_signal_dirty(signal) { function is_signal_dirty(signal) {
const flags = signal.f; const flags = signal.f;
if ((flags & DIRTY) !== 0 || signal.v === UNINITIALIZED) { if ((flags & DIRTY) !== 0 || signal.v === UNINITIALIZED) {
return true; return true;
} }
if ((flags & MAYBE_DIRTY) !== 0) { if ((flags & MAYBE_DIRTY) !== 0) {
const dependencies = /** @type {import('#client').Reaction} **/ (signal).d; const dependencies = /** @type {import('#client').Reaction} **/ (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];
if ((dependency.f & 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.f & DIRTY) !== 0) { if ((dependency.f & DIRTY) !== 0) {
if ((dependency.f & DERIVED) !== 0) { if ((dependency.f & DERIVED) !== 0) {
update_derived(/** @type {import('#client').Reaction} **/ (dependency), true); update_derived(/** @type {import('#client').Derived} **/ (dependency), true);
// Might have been mutated from above get. // Might have been mutated from above get.
if ((signal.f & DIRTY) !== 0) { if ((signal.f & DIRTY) !== 0) {
return true; return true;
@ -183,15 +188,17 @@ function is_signal_dirty(signal) {
return true; return true;
} }
} }
// If we're workig with an unowned derived signal, then we need to check // If we're workig with an unowned derived signal, then we need to check
// if our dependency write version is higher. If is is then we can assume // if our dependency write version is higher. If is is then we can assume
// that state has changed to a newer version and thus this unowned signal // that state has changed to a newer version and thus this unowned signal
// is also dirty. // is also dirty.
const is_unowned = (flags & UNOWNED) !== 0; const is_unowned = (flags & UNOWNED) !== 0;
const write_version = signal.w; const write_version = /** @type {import('#client').Derived} */ (signal).w;
const dep_write_version = dependency.w; const dep_write_version = dependency.w;
if (is_unowned && dep_write_version > write_version) { if (is_unowned && dep_write_version > write_version) {
signal.w = dep_write_version; /** @type {import('#client').Derived} */ (signal).w = dep_write_version;
return true; return true;
} }
} }
@ -212,15 +219,12 @@ function execute_signal_fn(signal) {
const previous_dependencies_index = current_dependencies_index; const previous_dependencies_index = current_dependencies_index;
const previous_untracked_writes = current_untracked_writes; const previous_untracked_writes = current_untracked_writes;
const previous_consumer = current_consumer; const previous_consumer = current_consumer;
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 = (flags & RENDER_EFFECT) !== 0;
const previous_untracking = current_untracking; const previous_untracking = current_untracking;
current_dependencies = /** @type {null | import('#client').ValueSignal[]} */ (null); current_dependencies = /** @type {null | import('#client').ValueSignal[]} */ (null);
current_dependencies_index = 0; current_dependencies_index = 0;
current_untracked_writes = null; current_untracked_writes = null;
current_consumer = signal; current_consumer = signal;
current_component_context = signal.x;
current_skip_consumer = !is_flushing_effect && (flags & UNOWNED) !== 0; current_skip_consumer = !is_flushing_effect && (flags & UNOWNED) !== 0;
current_untracking = false; current_untracking = false;
@ -292,7 +296,6 @@ function execute_signal_fn(signal) {
current_dependencies_index = previous_dependencies_index; current_dependencies_index = previous_dependencies_index;
current_untracked_writes = previous_untracked_writes; current_untracked_writes = previous_untracked_writes;
current_consumer = previous_consumer; current_consumer = previous_consumer;
current_component_context = previous_component_context;
current_skip_consumer = previous_skip_consumer; current_skip_consumer = previous_skip_consumer;
current_untracking = previous_untracking; current_untracking = previous_untracking;
} }
@ -363,38 +366,47 @@ function destroy_references(effect) {
} }
/** /**
* @param {import('#client').Effect} signal * @param {import('#client').Effect} effect
* @returns {void} * @returns {void}
*/ */
export function execute_effect(signal) { export function execute_effect(effect) {
if ((signal.f & DESTROYED) !== 0) { if ((effect.f & DESTROYED) !== 0) {
return; return;
} }
const teardown = signal.v;
const teardown = effect.v;
const previous_component_context = current_component_context;
const previous_effect = current_effect; const previous_effect = current_effect;
current_effect = signal; current_effect = effect;
const component_context = effect.ctx;
try { try {
if ((signal.f & BRANCH_EFFECT) === 0) { if ((effect.f & BRANCH_EFFECT) === 0) {
// branch effects (i.e. {#if ...} blocks) need to keep their references // branch effects (i.e. {#if ...} blocks) need to keep their references
// TODO their children should detach themselves from signal.r when destroyed // TODO their children should detach themselves from signal.r when destroyed
destroy_references(signal); destroy_references(effect);
} }
if (teardown !== null) { if (teardown !== null) {
teardown(); teardown();
} }
const possible_teardown = execute_signal_fn(signal);
current_component_context = component_context;
const possible_teardown = execute_signal_fn(effect);
if (typeof possible_teardown === 'function') { if (typeof possible_teardown === 'function') {
signal.v = possible_teardown; effect.v = possible_teardown;
} }
} finally { } finally {
current_component_context = previous_component_context;
current_effect = previous_effect; current_effect = previous_effect;
} }
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.f & PRE_EFFECT) !== 0 && (effect.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);
@ -549,7 +561,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.f & RENDER_EFFECT) !== 0 && effect.x === current_component_context) { if ((effect.f & RENDER_EFFECT) !== 0 && effect.ctx === 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--;
@ -566,7 +578,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.f & PRE_EFFECT) !== 0 && effect.x === context) { if ((effect.f & PRE_EFFECT) !== 0 && effect.ctx === 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--;
@ -641,7 +653,7 @@ export async function tick() {
/** /**
* @template V * @template V
* @param {import('#client').Reaction} signal * @param {import('#client').Derived} signal
* @param {boolean} force_schedule * @param {boolean} force_schedule
* @returns {void} * @returns {void}
*/ */
@ -728,10 +740,10 @@ export function get(signal) {
// we want to avoid tracking indirect dependencies // we want to avoid tracking indirect dependencies
const previous_inspect_fn = inspect_fn; const previous_inspect_fn = inspect_fn;
inspect_fn = null; inspect_fn = null;
update_derived(/** @type {import('#client').Reaction} **/ (signal), false); update_derived(/** @type {import('#client').Derived} **/ (signal), false);
inspect_fn = previous_inspect_fn; inspect_fn = previous_inspect_fn;
} else { } else {
update_derived(/** @type {import('#client').Reaction} **/ (signal), false); update_derived(/** @type {import('#client').Derived} **/ (signal), false);
} }
} }
return signal.v; return signal.v;
@ -781,7 +793,6 @@ export function set(signal, value) {
is_runes(null) && is_runes(null) &&
!ignore_mutation_validation && !ignore_mutation_validation &&
current_effect !== null && current_effect !== null &&
current_effect.c === null &&
(current_effect.f & CLEAN) !== 0 && (current_effect.f & CLEAN) !== 0 &&
(current_effect.f & MANAGED) === 0 (current_effect.f & MANAGED) === 0
) { ) {
@ -863,8 +874,7 @@ export function mutate(source, value) {
} }
/** /**
* @template V * @param {import('#client').ValueSignal} signal
* @param {import('#client').Signal<V>} signal
* @param {number} to_status * @param {number} to_status
* @param {boolean} force_schedule * @param {boolean} force_schedule
* @returns {void} * @returns {void}
@ -872,29 +882,39 @@ 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.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.f; const flags = consumer.f;
const unowned = (flags & UNOWNED) !== 0; const unowned = (flags & UNOWNED) !== 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
// skip if the consumer is the same as the current effect (except if we're not in runes or we // skip if the consumer is the same as the current effect (except if we're not in runes or we
// are in force schedule mode). // are in force schedule mode).
if ((!force_schedule || !runes) && consumer === current_effect) { if ((!force_schedule || !runes) && consumer === current_effect) {
continue; continue;
} }
set_signal_status(consumer, to_status); set_signal_status(consumer, to_status);
// If the signal is not clean, then skip over it with the exception of unowned signals that // If the signal is not clean, then skip over it with the exception of unowned signals that
// are already maybe dirty. Unowned signals might be dirty because they are not captured as part of an // are already maybe dirty. Unowned signals might be dirty because they are not captured as part of an
// effect. // effect.
const maybe_dirty = (flags & MAYBE_DIRTY) !== 0; const maybe_dirty = (flags & MAYBE_DIRTY) !== 0;
if ((flags & CLEAN) !== 0 || (maybe_dirty && unowned)) { if ((flags & CLEAN) !== 0 || (maybe_dirty && unowned)) {
if ((consumer.f & IS_EFFECT) !== 0) { if ((consumer.f & IS_EFFECT) !== 0) {
schedule_effect(/** @type {import('#client').Effect} */ (consumer), false); schedule_effect(/** @type {import('#client').Effect} */ (consumer), false);
} else { } else {
mark_signal_consumers(consumer, MAYBE_DIRTY, force_schedule); mark_signal_consumers(
/** @type {import('#client').ValueSignal} */ (consumer),
MAYBE_DIRTY,
force_schedule
);
} }
} }
} }
@ -945,9 +965,9 @@ export function untrack(fn) {
} }
const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN); const STATUS_MASK = ~(DIRTY | MAYBE_DIRTY | CLEAN);
/** /**
* @template V * @param {import('#client').Signal} signal
* @param {import('#client').Signal<V>} signal
* @param {number} status * @param {number} status
* @returns {void} * @returns {void}
*/ */

Loading…
Cancel
Save