|
|
|
|
@ -249,6 +249,10 @@ export class Batch {
|
|
|
|
|
reset_branch(e, t);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if (this.#pending === 0) {
|
|
|
|
|
batches.delete(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// clear effects. Those that are still needed will be rescheduled through unskipping the skipped branches.
|
|
|
|
|
this.#dirty_effects.clear();
|
|
|
|
|
this.#maybe_dirty_effects.clear();
|
|
|
|
|
@ -262,15 +266,19 @@ export class Batch {
|
|
|
|
|
flush_queued_effects(effects);
|
|
|
|
|
previous_batch = null;
|
|
|
|
|
|
|
|
|
|
if (this.#pending === 0) {
|
|
|
|
|
this.#commit();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.#deferred?.resolve();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var next_batch = /** @type {Batch | null} */ (/** @type {unknown} */ (current_batch));
|
|
|
|
|
|
|
|
|
|
// Edge case: During traversal new branches might create effects that run immediately and set state,
|
|
|
|
|
// causing an effect and therefore a root to be scheduled again. We need to traverse the current batch
|
|
|
|
|
// once more in that case - most of the time this will just clean up dirty branches.
|
|
|
|
|
if (this.#roots.length > 0) {
|
|
|
|
|
const batch = (next_batch ??= this);
|
|
|
|
|
batch.#roots.push(...this.#roots.filter((r) => !batch.#roots.includes(r)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (next_batch !== null) {
|
|
|
|
|
batches.add(next_batch);
|
|
|
|
|
|
|
|
|
|
@ -282,6 +290,10 @@ export class Batch {
|
|
|
|
|
|
|
|
|
|
next_batch.#process();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!batches.has(this)) {
|
|
|
|
|
this.#commit();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -425,74 +437,59 @@ export class Batch {
|
|
|
|
|
// in other words, we re-run block/async effects with the newly
|
|
|
|
|
// committed state, unless the batch in question has a more
|
|
|
|
|
// recent value for a given source
|
|
|
|
|
if (batches.size > 1) {
|
|
|
|
|
this.previous.clear();
|
|
|
|
|
|
|
|
|
|
var previous_batch = current_batch;
|
|
|
|
|
var previous_batch_values = batch_values;
|
|
|
|
|
var is_earlier = true;
|
|
|
|
|
|
|
|
|
|
for (const batch of batches) {
|
|
|
|
|
if (batch === this) {
|
|
|
|
|
is_earlier = false;
|
|
|
|
|
continue;
|
|
|
|
|
for (const batch of batches) {
|
|
|
|
|
var is_earlier = batch.id < this.id;
|
|
|
|
|
|
|
|
|
|
/** @type {Source[]} */
|
|
|
|
|
var sources = [];
|
|
|
|
|
|
|
|
|
|
for (const [source, value] of this.current) {
|
|
|
|
|
if (batch.current.has(source)) {
|
|
|
|
|
if (is_earlier && value !== batch.current.get(source)) {
|
|
|
|
|
// bring the value up to date
|
|
|
|
|
batch.current.set(source, value);
|
|
|
|
|
} else {
|
|
|
|
|
// same value or later batch has more recent value,
|
|
|
|
|
// no need to re-run these effects
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/** @type {Source[]} */
|
|
|
|
|
const sources = [];
|
|
|
|
|
|
|
|
|
|
for (const [source, value] of this.current) {
|
|
|
|
|
if (batch.current.has(source)) {
|
|
|
|
|
if (is_earlier && value !== batch.current.get(source)) {
|
|
|
|
|
// bring the value up to date
|
|
|
|
|
batch.current.set(source, value);
|
|
|
|
|
} else {
|
|
|
|
|
// same value or later batch has more recent value,
|
|
|
|
|
// no need to re-run these effects
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sources.push(source);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sources.push(source);
|
|
|
|
|
}
|
|
|
|
|
if (sources.length === 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (sources.length === 0) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
// Re-run async/block effects that depend on distinct values changed in both batches
|
|
|
|
|
var others = [...batch.current.keys()].filter((s) => !this.current.has(s));
|
|
|
|
|
if (others.length > 0) {
|
|
|
|
|
batch.activate();
|
|
|
|
|
|
|
|
|
|
// Re-run async/block effects that depend on distinct values changed in both batches
|
|
|
|
|
const others = [...batch.current.keys()].filter((s) => !this.current.has(s));
|
|
|
|
|
if (others.length > 0) {
|
|
|
|
|
batch.activate();
|
|
|
|
|
|
|
|
|
|
/** @type {Set<Value>} */
|
|
|
|
|
const marked = new Set();
|
|
|
|
|
/** @type {Map<Reaction, boolean>} */
|
|
|
|
|
const checked = new Map();
|
|
|
|
|
for (const source of sources) {
|
|
|
|
|
mark_effects(source, others, marked, checked);
|
|
|
|
|
}
|
|
|
|
|
/** @type {Set<Value>} */
|
|
|
|
|
var marked = new Set();
|
|
|
|
|
|
|
|
|
|
if (batch.#roots.length > 0) {
|
|
|
|
|
batch.apply();
|
|
|
|
|
/** @type {Map<Reaction, boolean>} */
|
|
|
|
|
var checked = new Map();
|
|
|
|
|
|
|
|
|
|
for (const root of batch.#roots) {
|
|
|
|
|
batch.#traverse(root, [], []);
|
|
|
|
|
}
|
|
|
|
|
for (var source of sources) {
|
|
|
|
|
mark_effects(source, others, marked, checked);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (batch.#roots.length > 0) {
|
|
|
|
|
batch.apply();
|
|
|
|
|
|
|
|
|
|
// TODO do we need to do anything with the dummy effect arrays?
|
|
|
|
|
for (var root of batch.#roots) {
|
|
|
|
|
batch.#traverse(root, [], []);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
batch.deactivate();
|
|
|
|
|
// TODO do we need to do anything with the dummy effect arrays?
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
current_batch = previous_batch;
|
|
|
|
|
batch_values = previous_batch_values;
|
|
|
|
|
batch.deactivate();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.#skipped_branches.clear();
|
|
|
|
|
batches.delete(this);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
@ -559,7 +556,10 @@ export class Batch {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
apply() {
|
|
|
|
|
if (!async_mode_flag || (!this.is_fork && batches.size === 1)) return;
|
|
|
|
|
if (!async_mode_flag || (!this.is_fork && batches.size === 1)) {
|
|
|
|
|
batch_values = null;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// if there are multiple batches, we are 'time travelling' —
|
|
|
|
|
// we need to override values with the ones in this batch...
|
|
|
|
|
|