fix: merge async batches on new dependencies

pull/18674/head
Kamil Jakubus 3 weeks ago
parent 10ceb91adc
commit 597609c53c

@ -171,6 +171,14 @@ export class Batch {
*/
#new_effects = [];
/**
* Values that were first read by a reaction while this batch was time travelling
* over an earlier batch that changed them. These reads connect the batches even
* though this batch did not write to the values itself.
* @type {Set<Value>}
*/
#new_dependencies = new Set();
/**
* Deferred effects (which run after async work has completed) that are DIRTY
* @type {Set<Effect>}
@ -482,6 +490,12 @@ export class Batch {
while (batch !== null) {
if (!batch.is_fork) {
for (const value of this.#new_dependencies) {
if (batch.current.has(value)) {
return batch;
}
}
// if the batches are connected, break
for (const [value, [, is_derived]] of this.current) {
if (batch.current.has(value) && !is_derived) {
@ -500,6 +514,10 @@ export class Batch {
* @param {Batch} batch
*/
#merge(batch) {
for (const value of batch.#new_dependencies) {
this.#new_dependencies.add(value);
}
for (const [source, value] of batch.current) {
if (!this.previous.has(source) && batch.previous.has(source)) {
this.previous.set(source, batch.previous.get(source));
@ -598,6 +616,28 @@ export class Batch {
}
}
/**
* If a reaction discovers a dependency that was changed by an earlier pending
* batch, connect the two batches and expose the current value while traversing.
* The current batch will be merged into the earlier one before it can commit.
* @param {Value} value
*/
capture_dependency(value) {
if (this.is_fork || this.current.has(value)) return;
var batch = this.#prev;
while (batch !== null) {
if (!batch.is_fork && batch.current.has(value)) {
this.#new_dependencies.add(value);
batch_values?.set(value, /** @type {[any, boolean]} */ (batch.current.get(value))[0]);
return;
}
batch = batch.#prev;
}
}
activate() {
current_batch = this;
}

@ -540,6 +540,14 @@ export function settled() {
export function get(signal) {
var flags = signal.f;
var is_derived = (flags & DERIVED) !== 0;
var is_new_dependency =
batch_values !== null &&
!untracking &&
((active_reaction !== null &&
(active_reaction.deps === null || !includes.call(active_reaction.deps, signal))) ||
(active_reaction === null &&
active_effect !== null &&
(active_effect.f & REACTION_RAN) === 0));
captured_signals?.add(signal);
@ -699,6 +707,10 @@ export function get(signal) {
}
if (batch_values?.has(signal)) {
if (is_new_dependency) {
(current_batch ?? previous_batch)?.capture_dependency(signal);
}
return batch_values.get(signal);
}

@ -16,20 +16,12 @@ export default test({
<button>x</button>
<button>y++</button>
<button>resolve</button>
world
` // if this does not show world - that would also be ok
`
);
resolve.click();
await tick();
assert.deepEqual(logs, [
'universe',
'world',
'$effect: world',
'$effect: universe',
'$effect: universe'
]);
// assert.deepEqual(logs, ['universe', 'universe', '$effect: universe', '$effect: universe']); // this would also be ok
assert.deepEqual(logs, ['universe', 'universe', '$effect: universe', '$effect: universe']);
assert.htmlEqual(
target.innerHTML,
`

@ -17,13 +17,7 @@ export default test({
<button>y++</button>
<button>resolve</button>
<hr>
world
"world"
world
world
world
"world"
` // if this does not show world "world" world world world "world" - then this would also be ok
`
);
resolve.click();

@ -29,13 +29,7 @@ export default test({
<button>y++</button>
<button>resolve</button>
<hr>
world
"world"
world
world
world
"world"
` // if this does not show world "world" world world world "world" - then this would also be ok
`
);
resolve.click();

@ -11,26 +11,19 @@ export default test({
y.click();
await tick();
assert.deepEqual(logs, ['universe', 'world', '$effect: world']);
assert.deepEqual(logs, ['universe', 'universe']);
assert.htmlEqual(
target.innerHTML,
`
<button>x</button>
<button>y++</button>
<button>resolve</button>
world
`
);
resolve.click();
await tick();
assert.deepEqual(logs, [
'universe',
'world',
'$effect: world',
'$effect: universe',
'$effect: universe'
]);
assert.deepEqual(logs, ['universe', 'universe', '$effect: universe', '$effect: universe']);
assert.htmlEqual(
target.innerHTML,
`

Loading…
Cancel
Save