Skip to content

Commit 46e9d2d

Browse files
chore: remove UNOWNED flag (#17105)
Fixes #17024 Fixes #17049 (comment) (and therefore everything that was still buggy in that issue I think) * chore: remove unowned check when calling `e.effect_in_unowned_derived` * WIP * all non-unit tests passing * tidy * WIP * WIP * WIP * note to self * fix * fix * hmm maybe not * try this * simplify * remove skip_reaction * docs * add changeset, in case this results in changed behaviour * Update packages/svelte/src/internal/client/reactivity/effects.js Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> * fix #17024 * fix comment * revert * fix * dry * changeset * fix WAS_MARKED logic * failing test (that uncovered other unrelated bug) + fix * fix: delete from batch_values on updates (#17115) * fix: delete from batch_values on updates This fixes a bug where a derived would still show its old value even after it was indirectly updated again within the same batch. This can for example happen by reading a derived on an effect, then writing to a source in that same effect that makes the derived update, and then read the derived value in a sibling effect - it still shows the old value without the fix. The fix is to _delete_ the value from batch_values, as it's now the newest value across all batches. In order to not prevent breakage on other batches we have to leave the status of deriveds as-is, i.e. within is_dirty and update_derived we cannot update its status. That might be a bit more inefficient as you now have to traverse the graph for each `get` of that derived (it's a bit like they are all disconnected) but we can always optimize that later if need be. Another advantage of this fix is that we can get rid of duplicate logic we had to add about unmarking and reconnecting deriveds, because that logic was only needed for the case where deriveds are read after they are updated, which now no longer hits that if-branch * keep derived cache, but clear it in mark_reactions (#17116) --------- Co-authored-by: Rich Harris <rich.harris@vercel.com> Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Simon Holthausen <simon.holthausen@vercel.com>
1 parent 7a24354 commit 46e9d2d

File tree

13 files changed

+212
-112
lines changed

13 files changed

+212
-112
lines changed

.changeset/four-paths-cheer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
chore: simplify connection/disconnection logic

.changeset/whole-webs-stick.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'svelte': patch
3+
---
4+
5+
fix: reconnect deriveds to effect tree when time-travelling

packages/svelte/src/internal/client/constants.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ export const BLOCK_EFFECT = 1 << 4;
66
export const BRANCH_EFFECT = 1 << 5;
77
export const ROOT_EFFECT = 1 << 6;
88
export const BOUNDARY_EFFECT = 1 << 7;
9+
/**
10+
* Indicates that a reaction is connected to an effect root — either it is an effect,
11+
* or it is a derived that is depended on by at least one effect. If a derived has
12+
* no dependents, we can disconnect it from the graph, allowing it to either be
13+
* GC'd or reconnected later if an effect comes to depend on it again
14+
*/
15+
export const CONNECTED = 1 << 9;
916
export const CLEAN = 1 << 10;
1017
export const DIRTY = 1 << 11;
1118
export const MAYBE_DIRTY = 1 << 12;
@@ -26,8 +33,6 @@ export const EFFECT_PRESERVED = 1 << 19;
2633
export const USER_EFFECT = 1 << 20;
2734

2835
// Flags exclusive to deriveds
29-
export const UNOWNED = 1 << 8;
30-
export const DISCONNECTED = 1 << 9;
3136
/**
3237
* Tells that we marked this derived and its reactions as visited during the "mark as (maybe) dirty"-phase.
3338
* Will be lifted during execution of the derived and during checking its dirty state (both are necessary

packages/svelte/src/internal/client/reactivity/deriveds.js

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,14 @@ import {
99
EFFECT_PRESERVED,
1010
MAYBE_DIRTY,
1111
STALE_REACTION,
12-
UNOWNED,
1312
ASYNC,
14-
WAS_MARKED
13+
WAS_MARKED,
14+
CONNECTED
1515
} from '#client/constants';
1616
import {
1717
active_reaction,
1818
active_effect,
1919
set_signal_status,
20-
skip_reaction,
2120
update_reaction,
2221
increment_write_version,
2322
set_active_effect,
@@ -27,7 +26,7 @@ import {
2726
import { equals, safe_equals } from './equality.js';
2827
import * as e from '../errors.js';
2928
import * as w from '../warnings.js';
30-
import { async_effect, destroy_effect, teardown } from './effects.js';
29+
import { async_effect, destroy_effect, effect_tracking, teardown } from './effects.js';
3130
import { eager_effects, internal_set, set_eager_effects, source } from './sources.js';
3231
import { get_stack } from '../dev/tracing.js';
3332
import { async_mode_flag, tracing_mode_flag } from '../../flags/index.js';
@@ -61,9 +60,7 @@ export function derived(fn) {
6160
? /** @type {Derived} */ (active_reaction)
6261
: null;
6362

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

368+
// During time traveling we don't want to reset the status so that
369+
// traversal of the graph in the other batches still happens
371370
if (batch_values !== null) {
372-
batch_values.set(derived, derived.v);
371+
// only cache the value if we're in a tracking context, otherwise we won't
372+
// clear the cache in `mark_reactions` when dependencies are updated
373+
if (effect_tracking()) {
374+
batch_values.set(derived, derived.v);
375+
}
373376
} else {
374-
var status =
375-
(skip_reaction || (derived.f & UNOWNED) !== 0) && derived.deps !== null ? MAYBE_DIRTY : CLEAN;
376-
377+
var status = (derived.f & CONNECTED) === 0 ? MAYBE_DIRTY : CLEAN;
377378
set_signal_status(derived, status);
378379
}
379380
}

packages/svelte/src/internal/client/reactivity/effects.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,15 +25,15 @@ import {
2525
ROOT_EFFECT,
2626
EFFECT_TRANSPARENT,
2727
DERIVED,
28-
UNOWNED,
2928
CLEAN,
3029
EAGER_EFFECT,
3130
HEAD_EFFECT,
3231
MAYBE_DIRTY,
3332
EFFECT_PRESERVED,
3433
STALE_REACTION,
3534
USER_EFFECT,
36-
ASYNC
35+
ASYNC,
36+
CONNECTED
3737
} from '#client/constants';
3838
import * as e from '../errors.js';
3939
import { DEV } from 'esm-env';
@@ -48,11 +48,11 @@ import { without_reactive_context } from '../dom/elements/bindings/shared.js';
4848
* @param {'$effect' | '$effect.pre' | '$inspect'} rune
4949
*/
5050
export function validate_effect(rune) {
51-
if (active_effect === null && active_reaction === null) {
52-
e.effect_orphan(rune);
53-
}
51+
if (active_effect === null) {
52+
if (active_reaction === null) {
53+
e.effect_orphan(rune);
54+
}
5455

55-
if (active_reaction !== null && (active_reaction.f & UNOWNED) !== 0 && active_effect === null) {
5656
e.effect_in_unowned_derived();
5757
}
5858

@@ -103,7 +103,7 @@ function create_effect(type, fn, sync, push = true) {
103103
deps: null,
104104
nodes_start: null,
105105
nodes_end: null,
106-
f: type | DIRTY,
106+
f: type | DIRTY | CONNECTED,
107107
first: null,
108108
fn,
109109
last: null,

packages/svelte/src/internal/client/reactivity/sources.js

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,18 @@ import {
2323
DIRTY,
2424
BRANCH_EFFECT,
2525
EAGER_EFFECT,
26-
UNOWNED,
2726
MAYBE_DIRTY,
2827
BLOCK_EFFECT,
2928
ROOT_EFFECT,
3029
ASYNC,
31-
WAS_MARKED
30+
WAS_MARKED,
31+
CONNECTED
3232
} from '#client/constants';
3333
import * as e from '../errors.js';
3434
import { legacy_mode_flag, tracing_mode_flag } from '../../flags/index.js';
3535
import { get_stack, tag_proxy } from '../dev/tracing.js';
3636
import { component_context, is_runes } from '../context.js';
37-
import { Batch, eager_block_effects, schedule_effect } from './batch.js';
37+
import { Batch, batch_values, eager_block_effects, schedule_effect } from './batch.js';
3838
import { proxy } from '../proxy.js';
3939
import { execute_derived } from './deriveds.js';
4040

@@ -211,7 +211,8 @@ export function internal_set(source, value) {
211211
if ((source.f & DIRTY) !== 0) {
212212
execute_derived(/** @type {Derived} */ (source));
213213
}
214-
set_signal_status(source, (source.f & UNOWNED) === 0 ? CLEAN : MAYBE_DIRTY);
214+
215+
set_signal_status(source, (source.f & CONNECTED) !== 0 ? CLEAN : MAYBE_DIRTY);
215216
}
216217

217218
source.wv = increment_write_version();
@@ -333,9 +334,17 @@ function mark_reactions(signal, status) {
333334
}
334335

335336
if ((flags & DERIVED) !== 0) {
337+
var derived = /** @type {Derived} */ (reaction);
338+
339+
batch_values?.delete(derived);
340+
336341
if ((flags & WAS_MARKED) === 0) {
337-
reaction.f |= WAS_MARKED;
338-
mark_reactions(/** @type {Derived} */ (reaction), MAYBE_DIRTY);
342+
// Only connected deriveds can be reliably unmarked right away
343+
if (flags & CONNECTED) {
344+
reaction.f |= WAS_MARKED;
345+
}
346+
347+
mark_reactions(derived, MAYBE_DIRTY);
339348
}
340349
} else if (not_dirty) {
341350
if ((flags & BLOCK_EFFECT) !== 0) {

0 commit comments

Comments
 (0)