|
|
|
|
@ -1,5 +1,6 @@
|
|
|
|
|
/** @import { Fork } from 'svelte' */
|
|
|
|
|
/** @import { Derived, Effect, Reaction, Source, Value } from '#client' */
|
|
|
|
|
/** @import { Boundary } from '../dom/blocks/boundary' */
|
|
|
|
|
import {
|
|
|
|
|
BLOCK_EFFECT,
|
|
|
|
|
BRANCH_EFFECT,
|
|
|
|
|
@ -17,7 +18,6 @@ import {
|
|
|
|
|
EAGER_EFFECT,
|
|
|
|
|
HEAD_EFFECT,
|
|
|
|
|
ERROR_VALUE,
|
|
|
|
|
WAS_MARKED,
|
|
|
|
|
MANAGED_EFFECT
|
|
|
|
|
} from '#client/constants';
|
|
|
|
|
import { async_mode_flag } from '../../flags/index.js';
|
|
|
|
|
@ -37,15 +37,7 @@ import { DEV } from 'esm-env';
|
|
|
|
|
import { invoke_error_boundary } from '../error-handling.js';
|
|
|
|
|
import { flush_eager_effects, old_values, set_eager_effects, source, update } from './sources.js';
|
|
|
|
|
import { eager_effect, unlink_effect } from './effects.js';
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @typedef {{
|
|
|
|
|
* parent: EffectTarget | null;
|
|
|
|
|
* effect: Effect | null;
|
|
|
|
|
* effects: Effect[];
|
|
|
|
|
* render_effects: Effect[];
|
|
|
|
|
* }} EffectTarget
|
|
|
|
|
*/
|
|
|
|
|
import { defer_effect } from './utils.js';
|
|
|
|
|
|
|
|
|
|
/** @type {Set<Batch>} */
|
|
|
|
|
const batches = new Set();
|
|
|
|
|
@ -161,16 +153,14 @@ export class Batch {
|
|
|
|
|
|
|
|
|
|
this.apply();
|
|
|
|
|
|
|
|
|
|
/** @type {EffectTarget} */
|
|
|
|
|
var target = {
|
|
|
|
|
parent: null,
|
|
|
|
|
effect: null,
|
|
|
|
|
effects: [],
|
|
|
|
|
render_effects: []
|
|
|
|
|
};
|
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
|
var effects = [];
|
|
|
|
|
|
|
|
|
|
/** @type {Effect[]} */
|
|
|
|
|
var render_effects = [];
|
|
|
|
|
|
|
|
|
|
for (const root of root_effects) {
|
|
|
|
|
this.#traverse_effect_tree(root, target);
|
|
|
|
|
this.#traverse_effect_tree(root, effects, render_effects);
|
|
|
|
|
// Note: #traverse_effect_tree runs block effects eagerly, which can schedule effects,
|
|
|
|
|
// which means queued_root_effects now may be filled again.
|
|
|
|
|
|
|
|
|
|
@ -183,16 +173,16 @@ export class Batch {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.is_deferred()) {
|
|
|
|
|
this.#defer_effects(target.effects);
|
|
|
|
|
this.#defer_effects(target.render_effects);
|
|
|
|
|
this.#defer_effects(render_effects);
|
|
|
|
|
this.#defer_effects(effects);
|
|
|
|
|
} else {
|
|
|
|
|
// If sources are written to, then work needs to happen in a separate batch, else prior sources would be mixed with
|
|
|
|
|
// newly updated sources, which could lead to infinite loops when effects run over and over again.
|
|
|
|
|
previous_batch = this;
|
|
|
|
|
current_batch = null;
|
|
|
|
|
|
|
|
|
|
flush_queued_effects(target.render_effects);
|
|
|
|
|
flush_queued_effects(target.effects);
|
|
|
|
|
flush_queued_effects(render_effects);
|
|
|
|
|
flush_queued_effects(effects);
|
|
|
|
|
|
|
|
|
|
previous_batch = null;
|
|
|
|
|
|
|
|
|
|
@ -206,13 +196,17 @@ export class Batch {
|
|
|
|
|
* Traverse the effect tree, executing effects or stashing
|
|
|
|
|
* them for later execution as appropriate
|
|
|
|
|
* @param {Effect} root
|
|
|
|
|
* @param {EffectTarget} target
|
|
|
|
|
* @param {Effect[]} effects
|
|
|
|
|
* @param {Effect[]} render_effects
|
|
|
|
|
*/
|
|
|
|
|
#traverse_effect_tree(root, target) {
|
|
|
|
|
#traverse_effect_tree(root, effects, render_effects) {
|
|
|
|
|
root.f ^= CLEAN;
|
|
|
|
|
|
|
|
|
|
var effect = root.first;
|
|
|
|
|
|
|
|
|
|
/** @type {Effect | null} */
|
|
|
|
|
var pending_boundary = null;
|
|
|
|
|
|
|
|
|
|
while (effect !== null) {
|
|
|
|
|
var flags = effect.f;
|
|
|
|
|
var is_branch = (flags & (BRANCH_EFFECT | ROOT_EFFECT)) !== 0;
|
|
|
|
|
@ -220,24 +214,32 @@ export class Batch {
|
|
|
|
|
|
|
|
|
|
var skip = is_skippable_branch || (flags & INERT) !== 0 || this.skipped_effects.has(effect);
|
|
|
|
|
|
|
|
|
|
if ((effect.f & BOUNDARY_EFFECT) !== 0 && effect.b?.is_pending()) {
|
|
|
|
|
target = {
|
|
|
|
|
parent: target,
|
|
|
|
|
effect,
|
|
|
|
|
effects: [],
|
|
|
|
|
render_effects: []
|
|
|
|
|
};
|
|
|
|
|
// Inside a `<svelte:boundary>` with a pending snippet,
|
|
|
|
|
// all effects are deferred until the boundary resolves
|
|
|
|
|
// (except block/async effects, which run immediately)
|
|
|
|
|
if (
|
|
|
|
|
async_mode_flag &&
|
|
|
|
|
pending_boundary === null &&
|
|
|
|
|
(flags & BOUNDARY_EFFECT) !== 0 &&
|
|
|
|
|
effect.b?.is_pending
|
|
|
|
|
) {
|
|
|
|
|
pending_boundary = effect;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!skip && effect.fn !== null) {
|
|
|
|
|
if (is_branch) {
|
|
|
|
|
effect.f ^= CLEAN;
|
|
|
|
|
} else if (
|
|
|
|
|
pending_boundary !== null &&
|
|
|
|
|
(flags & (EFFECT | RENDER_EFFECT | MANAGED_EFFECT)) !== 0
|
|
|
|
|
) {
|
|
|
|
|
/** @type {Boundary} */ (pending_boundary.b).defer_effect(effect);
|
|
|
|
|
} else if ((flags & EFFECT) !== 0) {
|
|
|
|
|
target.effects.push(effect);
|
|
|
|
|
effects.push(effect);
|
|
|
|
|
} else if (async_mode_flag && (flags & (RENDER_EFFECT | MANAGED_EFFECT)) !== 0) {
|
|
|
|
|
target.render_effects.push(effect);
|
|
|
|
|
render_effects.push(effect);
|
|
|
|
|
} else if (is_dirty(effect)) {
|
|
|
|
|
if ((effect.f & BLOCK_EFFECT) !== 0) this.#dirty_effects.add(effect);
|
|
|
|
|
if ((flags & BLOCK_EFFECT) !== 0) this.#dirty_effects.add(effect);
|
|
|
|
|
update_effect(effect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -253,14 +255,8 @@ export class Batch {
|
|
|
|
|
effect = effect.next;
|
|
|
|
|
|
|
|
|
|
while (effect === null && parent !== null) {
|
|
|
|
|
if (parent === target.effect) {
|
|
|
|
|
// TODO rather than traversing into pending boundaries and deferring the effects,
|
|
|
|
|
// could we just attach the effects _to_ the pending boundary and schedule them
|
|
|
|
|
// once the boundary is ready?
|
|
|
|
|
this.#defer_effects(target.effects);
|
|
|
|
|
this.#defer_effects(target.render_effects);
|
|
|
|
|
|
|
|
|
|
target = /** @type {EffectTarget} */ (target.parent);
|
|
|
|
|
if (parent === pending_boundary) {
|
|
|
|
|
pending_boundary = null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
effect = parent.next;
|
|
|
|
|
@ -273,36 +269,8 @@ export class Batch {
|
|
|
|
|
* @param {Effect[]} effects
|
|
|
|
|
*/
|
|
|
|
|
#defer_effects(effects) {
|
|
|
|
|
for (const e of effects) {
|
|
|
|
|
if ((e.f & DIRTY) !== 0) {
|
|
|
|
|
this.#dirty_effects.add(e);
|
|
|
|
|
} else if ((e.f & MAYBE_DIRTY) !== 0) {
|
|
|
|
|
this.#maybe_dirty_effects.add(e);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Since we're not executing these effects now, we need to clear any WAS_MARKED flags
|
|
|
|
|
// so that other batches can correctly reach these effects during their own traversal
|
|
|
|
|
this.#clear_marked(e.deps);
|
|
|
|
|
|
|
|
|
|
// mark as clean so they get scheduled if they depend on pending async state
|
|
|
|
|
set_signal_status(e, CLEAN);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @param {Value[] | null} deps
|
|
|
|
|
*/
|
|
|
|
|
#clear_marked(deps) {
|
|
|
|
|
if (deps === null) return;
|
|
|
|
|
|
|
|
|
|
for (const dep of deps) {
|
|
|
|
|
if ((dep.f & DERIVED) === 0 || (dep.f & WAS_MARKED) === 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dep.f ^= WAS_MARKED;
|
|
|
|
|
|
|
|
|
|
this.#clear_marked(/** @type {Derived} */ (dep).deps);
|
|
|
|
|
for (var i = 0; i < effects.length; i += 1) {
|
|
|
|
|
defer_effect(effects[i], this.#dirty_effects, this.#maybe_dirty_effects);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -383,14 +351,6 @@ export class Batch {
|
|
|
|
|
var previous_batch_values = batch_values;
|
|
|
|
|
var is_earlier = true;
|
|
|
|
|
|
|
|
|
|
/** @type {EffectTarget} */
|
|
|
|
|
var dummy_target = {
|
|
|
|
|
parent: null,
|
|
|
|
|
effect: null,
|
|
|
|
|
effects: [],
|
|
|
|
|
render_effects: []
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
for (const batch of batches) {
|
|
|
|
|
if (batch === this) {
|
|
|
|
|
is_earlier = false;
|
|
|
|
|
@ -439,10 +399,10 @@ export class Batch {
|
|
|
|
|
batch.apply();
|
|
|
|
|
|
|
|
|
|
for (const root of queued_root_effects) {
|
|
|
|
|
batch.#traverse_effect_tree(root, dummy_target);
|
|
|
|
|
batch.#traverse_effect_tree(root, [], []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TODO do we need to do anything with `target`? defer block effects?
|
|
|
|
|
// TODO do we need to do anything with the dummy effect arrays?
|
|
|
|
|
|
|
|
|
|
batch.deactivate();
|
|
|
|
|
}
|
|
|
|
|
|