Skip to content

Commit 15873ab

Browse files
Rich-Harristrueadm
andauthored
chore: only call mark_reactions when updating a source (#12272)
* only call mark_reactions when updating a source * move mark_reactions to where it belongs, remove force_schedule stuff * tweak * move inspect_effects to where it is used * this is wrong * remove obsolete comment * tweak * reconnect on get * try marking unowneds as clean * tidy up * tidy up * simplify * typo * don't bother caching length * fix lint --------- Co-authored-by: Dominic Gannaway <[email protected]>
1 parent d57491f commit 15873ab

File tree

5 files changed

+96
-108
lines changed

5 files changed

+96
-108
lines changed

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

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import {
44
current_effect,
55
remove_reactions,
66
set_signal_status,
7-
mark_reactions,
87
current_skip_reaction,
98
update_reaction,
109
destroy_effect_children,
@@ -39,7 +38,7 @@ export function derived(fn) {
3938
};
4039

4140
if (current_reaction !== null && (current_reaction.f & DERIVED) !== 0) {
42-
var current_derived = /** @type {import('#client').Derived<V>} */ (current_reaction);
41+
var current_derived = /** @type {import('#client').Derived} */ (current_reaction);
4342
if (current_derived.deriveds === null) {
4443
current_derived.deriveds = [signal];
4544
} else {
@@ -100,7 +99,6 @@ export function update_derived(derived) {
10099
if (!derived.equals(value)) {
101100
derived.v = value;
102101
derived.version = increment_version();
103-
mark_reactions(derived, DIRTY, false);
104102
}
105103
}
106104

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,8 @@ function create_effect(type, fn, sync, push = true) {
9494
parent: is_root ? null : current_effect,
9595
prev: null,
9696
teardown: null,
97-
transitions: null
97+
transitions: null,
98+
version: 0
9899
};
99100

100101
if (DEV) {

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

Lines changed: 54 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,20 +7,28 @@ import {
77
current_untracked_writes,
88
get,
99
is_runes,
10-
mark_reactions,
1110
schedule_effect,
1211
set_current_untracked_writes,
1312
set_signal_status,
1413
untrack,
1514
increment_version,
16-
update_effect,
17-
inspect_effects
15+
update_effect
1816
} from '../runtime.js';
1917
import { equals, safe_equals } from './equality.js';
20-
import { CLEAN, DERIVED, DIRTY, BRANCH_EFFECT } from '../constants.js';
18+
import {
19+
CLEAN,
20+
DERIVED,
21+
DIRTY,
22+
BRANCH_EFFECT,
23+
INSPECT_EFFECT,
24+
UNOWNED,
25+
MAYBE_DIRTY
26+
} from '../constants.js';
2127
import { UNINITIALIZED } from '../../../constants.js';
2228
import * as e from '../errors.js';
2329

30+
let inspect_effects = new Set();
31+
2432
/**
2533
* @template V
2634
* @param {V} v
@@ -91,7 +99,7 @@ export function set(source, value) {
9199
source.v = value;
92100
source.version = increment_version();
93101

94-
mark_reactions(source, DIRTY, true);
102+
mark_reactions(source, DIRTY);
95103

96104
// If the current signal is running for the first time, it won't have any
97105
// reactions as we only allocate and assign the reactions after the signal
@@ -132,3 +140,44 @@ export function set(source, value) {
132140

133141
return value;
134142
}
143+
144+
/**
145+
* @param {import('#client').Value} signal
146+
* @param {number} status should be DIRTY or MAYBE_DIRTY
147+
* @returns {void}
148+
*/
149+
function mark_reactions(signal, status) {
150+
var reactions = signal.reactions;
151+
if (reactions === null) return;
152+
153+
var runes = is_runes();
154+
var length = reactions.length;
155+
156+
for (var i = 0; i < length; i++) {
157+
var reaction = reactions[i];
158+
var flags = reaction.f;
159+
160+
// Skip any effects that are already dirty
161+
if ((flags & DIRTY) !== 0) continue;
162+
163+
// In legacy mode, skip the current effect to prevent infinite loops
164+
if (!runes && reaction === current_effect) continue;
165+
166+
// Inspect effects need to run immediately, so that the stack trace makes sense
167+
if (DEV && (flags & INSPECT_EFFECT) !== 0) {
168+
inspect_effects.add(reaction);
169+
continue;
170+
}
171+
172+
set_signal_status(reaction, status);
173+
174+
// If the signal a) was previously clean or b) is an unowned derived, then mark it
175+
if ((flags & (CLEAN | UNOWNED)) !== 0) {
176+
if ((flags & DERIVED) !== 0) {
177+
mark_reactions(/** @type {import('#client').Derived} */ (reaction), MAYBE_DIRTY);
178+
} else {
179+
schedule_effect(/** @type {import('#client').Effect} */ (reaction));
180+
}
181+
}
182+
}
183+
}

packages/svelte/src/internal/client/reactivity/types.d.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import type { ComponentContext, Dom, Equals, TemplateNode, TransitionManager } f
33
export interface Signal {
44
/** Flags bitmask */
55
f: number;
6+
/** Write version */
7+
version: number;
68
}
79

810
export interface Value<V = unknown> extends Signal {
@@ -12,8 +14,6 @@ export interface Value<V = unknown> extends Signal {
1214
equals: Equals;
1315
/** The latest value for this signal */
1416
v: V;
15-
/** Write version */
16-
version: number;
1717
}
1818

1919
export interface Reaction extends Signal {

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

Lines changed: 37 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,7 @@ import {
2929
ROOT_EFFECT,
3030
LEGACY_DERIVED_PROP,
3131
DISCONNECTED,
32-
STATE_FROZEN_SYMBOL,
33-
INSPECT_EFFECT
32+
STATE_FROZEN_SYMBOL
3433
} from './constants.js';
3534
import { flush_tasks } from './dom/task.js';
3635
import { add_owner } from './dev/ownership.js';
@@ -63,8 +62,6 @@ export function set_is_destroying_effect(value) {
6362
is_destroying_effect = value;
6463
}
6564

66-
export let inspect_effects = new Set();
67-
6865
// Handle effect queues
6966

7067
/** @type {import('#client').Effect[]} */
@@ -164,77 +161,44 @@ export function is_runes() {
164161
*/
165162
export function check_dirtiness(reaction) {
166163
var flags = reaction.f;
167-
var is_dirty = (flags & DIRTY) !== 0;
168164

169-
if (is_dirty) {
165+
if ((flags & DIRTY) !== 0) {
170166
return true;
171167
}
172168

173-
var is_unowned = (flags & UNOWNED) !== 0;
174-
var is_disconnected = (flags & DISCONNECTED) !== 0;
175-
176169
if ((flags & MAYBE_DIRTY) !== 0) {
177170
var dependencies = reaction.deps;
178171

179172
if (dependencies !== null) {
180-
var length = dependencies.length;
181-
var reactions;
173+
var is_unowned = (flags & UNOWNED) !== 0;
182174

183-
for (var i = 0; i < length; i++) {
175+
for (var i = 0; i < dependencies.length; i++) {
184176
var dependency = dependencies[i];
185177

186-
if (!is_dirty && check_dirtiness(/** @type {import('#client').Derived} */ (dependency))) {
187-
update_derived(/** @type {import('#client').Derived} **/ (dependency));
178+
if (check_dirtiness(/** @type {import('#client').Derived} */ (dependency))) {
179+
update_derived(/** @type {import('#client').Derived} */ (dependency));
188180
}
189181

190-
if ((reaction.f & DIRTY) !== 0) {
191-
// `reaction` might now be dirty, as a result of calling `update_derived`
182+
if (dependency.version > reaction.version) {
192183
return true;
193184
}
194185

195186
if (is_unowned) {
196-
// If we're working with an unowned derived signal, then we need to check
197-
// if our dependency write version is higher. If it is then we can assume
198-
// that state has changed to a newer version and thus this unowned signal
199-
// is also dirty.
200-
if (dependency.version > /** @type {import('#client').Derived} */ (reaction).version) {
201-
return true;
202-
}
203-
187+
// TODO is there a more logical place to do this work?
204188
if (!current_skip_reaction && !dependency?.reactions?.includes(reaction)) {
205189
// If we are working with an unowned signal as part of an effect (due to !current_skip_reaction)
206190
// and the version hasn't changed, we still need to check that this reaction
207191
// if linked to the dependency source – otherwise future updates will not be caught.
208192
(dependency.reactions ??= []).push(reaction);
209193
}
210-
} else if (is_disconnected) {
211-
// It might be that the derived was was dereferenced from its dependencies but has now come alive again.
212-
// In thise case, we need to re-attach it to the graph and mark it dirty if any of its dependencies have
213-
// changed since.
214-
if (dependency.version > /** @type {import('#client').Derived} */ (reaction).version) {
215-
is_dirty = true;
216-
}
217-
218-
reactions = dependency.reactions;
219-
if (reactions === null) {
220-
dependency.reactions = [reaction];
221-
} else if (!reactions.includes(reaction)) {
222-
reactions.push(reaction);
223-
}
224194
}
225195
}
226196
}
227197

228-
// Unowned signals are always maybe dirty, as we instead check their dependency versions.
229-
if (!is_unowned) {
230-
set_signal_status(reaction, CLEAN);
231-
}
232-
if (is_disconnected) {
233-
reaction.f ^= DISCONNECTED;
234-
}
198+
set_signal_status(reaction, CLEAN);
235199
}
236200

237-
return is_dirty;
201+
return false;
238202
}
239203

240204
/**
@@ -490,6 +454,8 @@ export function update_effect(effect) {
490454
execute_effect_teardown(effect);
491455
var teardown = update_reaction(effect);
492456
effect.teardown = typeof teardown === 'function' ? teardown : null;
457+
458+
effect.version = current_version;
493459
} catch (error) {
494460
handle_error(/** @type {Error} */ (error), effect, current_component_context);
495461
} finally {
@@ -798,11 +764,31 @@ export function get(signal) {
798764
}
799765
}
800766

801-
if (
802-
(flags & DERIVED) !== 0 &&
803-
check_dirtiness(/** @type {import('#client').Derived} */ (signal))
804-
) {
805-
update_derived(/** @type {import('#client').Derived} **/ (signal));
767+
if ((flags & DERIVED) !== 0) {
768+
var derived = /** @type {import('#client').Derived} */ (signal);
769+
770+
if (check_dirtiness(derived)) {
771+
update_derived(derived);
772+
}
773+
774+
if ((flags & DISCONNECTED) !== 0) {
775+
// reconnect to the graph
776+
deps = derived.deps;
777+
778+
if (deps !== null) {
779+
for (var i = 0; i < deps.length; i++) {
780+
var dep = deps[i];
781+
var reactions = dep.reactions;
782+
if (reactions === null) {
783+
dep.reactions = [derived];
784+
} else if (!reactions.includes(derived)) {
785+
reactions.push(derived);
786+
}
787+
}
788+
}
789+
790+
derived.f ^= DISCONNECTED;
791+
}
806792
}
807793

808794
return signal.v;
@@ -845,52 +831,6 @@ export function invalidate_inner_signals(fn) {
845831
}
846832
}
847833

848-
/**
849-
* @param {import('#client').Value} signal
850-
* @param {number} status should be DIRTY or MAYBE_DIRTY
851-
* @param {boolean} force_schedule
852-
* @returns {void}
853-
*/
854-
export function mark_reactions(signal, status, force_schedule) {
855-
var reactions = signal.reactions;
856-
if (reactions === null) return;
857-
858-
var runes = is_runes();
859-
var length = reactions.length;
860-
861-
for (var i = 0; i < length; i++) {
862-
var reaction = reactions[i];
863-
var flags = reaction.f;
864-
865-
if (DEV && (flags & INSPECT_EFFECT) !== 0) {
866-
inspect_effects.add(reaction);
867-
continue;
868-
}
869-
870-
// We skip any effects that are already dirty. Additionally, we also
871-
// skip if the reaction is the same as the current effect (except if we're not in runes or we
872-
// are in force schedule mode).
873-
if ((flags & DIRTY) !== 0 || ((!force_schedule || !runes) && reaction === current_effect)) {
874-
continue;
875-
}
876-
877-
set_signal_status(reaction, status);
878-
879-
// If the signal a) was previously clean or b) is an unowned derived, then mark it
880-
if ((flags & (CLEAN | UNOWNED)) !== 0) {
881-
if ((flags & DERIVED) !== 0) {
882-
mark_reactions(
883-
/** @type {import('#client').Derived} */ (reaction),
884-
MAYBE_DIRTY,
885-
force_schedule
886-
);
887-
} else {
888-
schedule_effect(/** @type {import('#client').Effect} */ (reaction));
889-
}
890-
}
891-
}
892-
}
893-
894834
/**
895835
* Use `untrack` to prevent something from being treated as an `$effect`/`$derived` dependency.
896836
*

0 commit comments

Comments
 (0)