Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
64f078d
chore: remove unowned check when calling `e.effect_in_unowned_derived`
Rich-Harris Nov 3, 2025
c9d26db
WIP
Rich-Harris Nov 3, 2025
db21188
all non-unit tests passing
Rich-Harris Nov 3, 2025
b5bb6fb
tidy
Rich-Harris Nov 3, 2025
625d289
WIP
Rich-Harris Nov 3, 2025
6700adb
WIP
Rich-Harris Nov 3, 2025
697fba0
WIP
Rich-Harris Nov 3, 2025
6eb4dee
note to self
Rich-Harris Nov 3, 2025
93f277d
fix
Rich-Harris Nov 3, 2025
e1fbee5
fix
Rich-Harris Nov 3, 2025
b61d1b0
hmm maybe not
Rich-Harris Nov 3, 2025
5b42362
Merge branch 'main' into remove-unowned
Rich-Harris Nov 3, 2025
15f9f85
try this
Rich-Harris Nov 4, 2025
67f544e
simplify
Rich-Harris Nov 4, 2025
d1149d6
remove skip_reaction
Rich-Harris Nov 4, 2025
d5c7605
docs
Rich-Harris Nov 4, 2025
0bd13a9
add changeset, in case this results in changed behaviour
Rich-Harris Nov 4, 2025
3197022
Update packages/svelte/src/internal/client/reactivity/effects.js
Rich-Harris Nov 4, 2025
4d5209c
fix #17024
Rich-Harris Nov 4, 2025
b8fc364
fix comment
Rich-Harris Nov 4, 2025
3d4fa95
revert
Rich-Harris Nov 4, 2025
027194a
fix
Rich-Harris Nov 4, 2025
69290af
dry
Rich-Harris Nov 4, 2025
c96eb44
changeset
Rich-Harris Nov 4, 2025
1032b31
fix WAS_MARKED logic
dummdidumm Nov 4, 2025
22435b2
failing test (that uncovered other unrelated bug) + fix
dummdidumm Nov 5, 2025
d55107f
fix: delete from batch_values on updates (#17115)
dummdidumm Nov 5, 2025
df077f3
tidy up
Rich-Harris Nov 5, 2025
66d6d4b
Merge branch 'main' into remove-unowned
Rich-Harris Nov 5, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/four-paths-cheer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

chore: simplify connection/disconnection logic
5 changes: 5 additions & 0 deletions .changeset/whole-webs-stick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte': patch
---

fix: reconnect deriveds to effect tree when time-travelling
9 changes: 7 additions & 2 deletions packages/svelte/src/internal/client/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ export const BLOCK_EFFECT = 1 << 4;
export const BRANCH_EFFECT = 1 << 5;
export const ROOT_EFFECT = 1 << 6;
export const BOUNDARY_EFFECT = 1 << 7;
/**
* Indicates that a reaction is connected to an effect root — either it is an effect,
* or it is a derived that is depended on by at least one effect. If a derived has
* no dependents, we can disconnect it from the graph, allowing it to either be
* GC'd or reconnected later if an effect comes to depend on it again
*/
export const CONNECTED = 1 << 9;
export const CLEAN = 1 << 10;
export const DIRTY = 1 << 11;
export const MAYBE_DIRTY = 1 << 12;
Expand All @@ -26,8 +33,6 @@ export const EFFECT_PRESERVED = 1 << 19;
export const USER_EFFECT = 1 << 20;

// Flags exclusive to deriveds
export const UNOWNED = 1 << 8;
export const DISCONNECTED = 1 << 9;
/**
* Tells that we marked this derived and its reactions as visited during the "mark as (maybe) dirty"-phase.
* Will be lifted during execution of the derived and during checking its dirty state (both are necessary
Expand Down
23 changes: 12 additions & 11 deletions packages/svelte/src/internal/client/reactivity/deriveds.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@ import {
EFFECT_PRESERVED,
MAYBE_DIRTY,
STALE_REACTION,
UNOWNED,
ASYNC,
WAS_MARKED
WAS_MARKED,
CONNECTED
} from '#client/constants';
import {
active_reaction,
active_effect,
set_signal_status,
skip_reaction,
update_reaction,
increment_write_version,
set_active_effect,
Expand All @@ -27,7 +26,7 @@ import {
import { equals, safe_equals } from './equality.js';
import * as e from '../errors.js';
import * as w from '../warnings.js';
import { async_effect, destroy_effect, teardown } from './effects.js';
import { async_effect, destroy_effect, effect_tracking, teardown } from './effects.js';
import { eager_effects, internal_set, set_eager_effects, source } from './sources.js';
import { get_stack } from '../dev/tracing.js';
import { async_mode_flag, tracing_mode_flag } from '../../flags/index.js';
Expand Down Expand Up @@ -61,9 +60,7 @@ export function derived(fn) {
? /** @type {Derived} */ (active_reaction)
: null;

if (active_effect === null || (parent_derived !== null && (parent_derived.f & UNOWNED) !== 0)) {
flags |= UNOWNED;
} else {
if (active_effect !== null) {
// Since deriveds are evaluated lazily, any effects created inside them are
// created too late to ensure that the parent effect is added to the tree
active_effect.f |= EFFECT_PRESERVED;
Expand Down Expand Up @@ -368,12 +365,16 @@ export function update_derived(derived) {
return;
}

// During time traveling we don't want to reset the status so that
// traversal of the graph in the other batches still happens
if (batch_values !== null) {
batch_values.set(derived, derived.v);
// only cache the value if we're in a tracking context, otherwise we won't
// clear the cache in `mark_reactions` when dependencies are updated
if (effect_tracking()) {
batch_values.set(derived, derived.v);
}
} else {
var status =
(skip_reaction || (derived.f & UNOWNED) !== 0) && derived.deps !== null ? MAYBE_DIRTY : CLEAN;

var status = (derived.f & CONNECTED) === 0 ? MAYBE_DIRTY : CLEAN;
set_signal_status(derived, status);
}
}
14 changes: 7 additions & 7 deletions packages/svelte/src/internal/client/reactivity/effects.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ import {
ROOT_EFFECT,
EFFECT_TRANSPARENT,
DERIVED,
UNOWNED,
CLEAN,
EAGER_EFFECT,
HEAD_EFFECT,
MAYBE_DIRTY,
EFFECT_PRESERVED,
STALE_REACTION,
USER_EFFECT,
ASYNC
ASYNC,
CONNECTED
} from '#client/constants';
import * as e from '../errors.js';
import { DEV } from 'esm-env';
Expand All @@ -48,11 +48,11 @@ import { without_reactive_context } from '../dom/elements/bindings/shared.js';
* @param {'$effect' | '$effect.pre' | '$inspect'} rune
*/
export function validate_effect(rune) {
if (active_effect === null && active_reaction === null) {
e.effect_orphan(rune);
}
if (active_effect === null) {
if (active_reaction === null) {
e.effect_orphan(rune);
}

if (active_reaction !== null && (active_reaction.f & UNOWNED) !== 0 && active_effect === null) {
e.effect_in_unowned_derived();
}

Expand Down Expand Up @@ -103,7 +103,7 @@ function create_effect(type, fn, sync, push = true) {
deps: null,
nodes_start: null,
nodes_end: null,
f: type | DIRTY,
f: type | DIRTY | CONNECTED,
first: null,
fn,
last: null,
Expand Down
21 changes: 15 additions & 6 deletions packages/svelte/src/internal/client/reactivity/sources.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,18 @@ import {
DIRTY,
BRANCH_EFFECT,
EAGER_EFFECT,
UNOWNED,
MAYBE_DIRTY,
BLOCK_EFFECT,
ROOT_EFFECT,
ASYNC,
WAS_MARKED
WAS_MARKED,
CONNECTED
} from '#client/constants';
import * as e from '../errors.js';
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
import { get_stack, tag_proxy } from '../dev/tracing.js';
import { component_context, is_runes } from '../context.js';
import { Batch, eager_block_effects, schedule_effect } from './batch.js';
import { Batch, batch_values, eager_block_effects, schedule_effect } from './batch.js';
import { proxy } from '../proxy.js';
import { execute_derived } from './deriveds.js';

Expand Down Expand Up @@ -211,7 +211,8 @@ export function internal_set(source, value) {
if ((source.f & DIRTY) !== 0) {
execute_derived(/** @type {Derived} */ (source));
}
set_signal_status(source, (source.f & UNOWNED) === 0 ? CLEAN : MAYBE_DIRTY);

set_signal_status(source, (source.f & CONNECTED) !== 0 ? CLEAN : MAYBE_DIRTY);
}

source.wv = increment_write_version();
Expand Down Expand Up @@ -333,9 +334,17 @@ function mark_reactions(signal, status) {
}

if ((flags & DERIVED) !== 0) {
var derived = /** @type {Derived} */ (reaction);

batch_values?.delete(derived);

if ((flags & WAS_MARKED) === 0) {
reaction.f |= WAS_MARKED;
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
// Only connected deriveds can be reliably unmarked right away
if (flags & CONNECTED) {
reaction.f |= WAS_MARKED;
}

mark_reactions(derived, MAYBE_DIRTY);
}
} else if (not_dirty) {
if ((flags & BLOCK_EFFECT) !== 0) {
Expand Down
Loading
Loading