|
1 | | -/** @import { Derived, Effect, Source, Value } from '#client' */ |
| 1 | +/** @import { Derived, Effect, Reaction, Source, Value } from '#client' */ |
2 | 2 | import { |
3 | 3 | BLOCK_EFFECT, |
4 | 4 | BRANCH_EFFECT, |
@@ -335,31 +335,43 @@ export class Batch { |
335 | 335 | continue; |
336 | 336 | } |
337 | 337 |
|
| 338 | + /** @type {Source[]} */ |
| 339 | + const sources = []; |
| 340 | + |
338 | 341 | for (const [source, value] of this.current) { |
339 | 342 | if (batch.current.has(source)) { |
340 | | - if (is_earlier) { |
| 343 | + if (is_earlier && value !== batch.current.get(source)) { |
341 | 344 | // bring the value up to date |
342 | 345 | batch.current.set(source, value); |
343 | 346 | } else { |
344 | | - // later batch has more recent value, |
| 347 | + // same value or later batch has more recent value, |
345 | 348 | // no need to re-run these effects |
346 | 349 | continue; |
347 | 350 | } |
348 | 351 | } |
349 | 352 |
|
350 | | - mark_effects(source); |
| 353 | + sources.push(source); |
351 | 354 | } |
352 | 355 |
|
353 | | - if (queued_root_effects.length > 0) { |
354 | | - current_batch = batch; |
355 | | - const revert = Batch.apply(batch); |
356 | | - |
357 | | - for (const root of queued_root_effects) { |
358 | | - batch.#traverse_effect_tree(root); |
| 356 | + // Only reschedule an async effect if it depends on something else than |
| 357 | + // the sources that were updated in this batch and that something else changed. |
| 358 | + const others = [...batch.current.keys()].filter((s) => !this.current.has(s)); |
| 359 | + if (others.length > 0) { |
| 360 | + for (const source of sources) { |
| 361 | + mark_effects(source, others); |
359 | 362 | } |
360 | 363 |
|
361 | | - queued_root_effects = []; |
362 | | - revert(); |
| 364 | + if (queued_root_effects.length > 0) { |
| 365 | + current_batch = batch; |
| 366 | + const revert = Batch.apply(batch); |
| 367 | + |
| 368 | + for (const root of queued_root_effects) { |
| 369 | + batch.#traverse_effect_tree(root); |
| 370 | + } |
| 371 | + |
| 372 | + queued_root_effects = []; |
| 373 | + revert(); |
| 374 | + } |
363 | 375 | } |
364 | 376 | } |
365 | 377 |
|
@@ -640,17 +652,34 @@ function flush_queued_effects(effects) { |
640 | 652 |
|
641 | 653 | /** |
642 | 654 | * This is similar to `mark_reactions`, but it only marks async/block effects |
643 | | - * so that these can re-run after another batch has been committed |
| 655 | + * depending on one of the sources, so that these effects can re-run after |
| 656 | + * another batch has been committed |
644 | 657 | * @param {Value} value |
| 658 | + * @param {Source[]} sources |
645 | 659 | */ |
646 | | -function mark_effects(value) { |
| 660 | +function mark_effects(value, sources) { |
647 | 661 | if (value.reactions !== null) { |
| 662 | + /** @param {Reaction} reaction */ |
| 663 | + const depends_on_sources = (reaction) => { |
| 664 | + if (reaction.deps !== null) { |
| 665 | + for (const dep of reaction.deps) { |
| 666 | + if (sources.includes(dep)) { |
| 667 | + return true; |
| 668 | + } |
| 669 | + if ((dep.f & DERIVED) !== 0 && depends_on_sources(/** @type {Derived} */ (dep))) { |
| 670 | + return true; |
| 671 | + } |
| 672 | + } |
| 673 | + } |
| 674 | + return false; |
| 675 | + }; |
| 676 | + |
648 | 677 | for (const reaction of value.reactions) { |
649 | 678 | const flags = reaction.f; |
650 | 679 |
|
651 | 680 | if ((flags & DERIVED) !== 0) { |
652 | | - mark_effects(/** @type {Derived} */ (reaction)); |
653 | | - } else if ((flags & (ASYNC | BLOCK_EFFECT)) !== 0) { |
| 681 | + mark_effects(/** @type {Derived} */ (reaction), sources); |
| 682 | + } else if ((flags & (ASYNC | BLOCK_EFFECT)) !== 0 && depends_on_sources(reaction)) { |
654 | 683 | set_signal_status(reaction, DIRTY); |
655 | 684 | schedule_effect(/** @type {Effect} */ (reaction)); |
656 | 685 | } |
|
0 commit comments