more messy experimentation

async-another-try
Simon Holthausen 1 week ago
parent e017878175
commit 9143c90b04
No known key found for this signature in database

@ -112,7 +112,7 @@ export class Batch {
id = uid++;
/** True as soon as `#process` was called */
#started = false;
started = false;
// TODO temporary
get next() {
@ -125,6 +125,9 @@ export class Batch {
linked = true;
/** @type {Map<Effect, number>} */
stale_effects = new Map();
/** @type {Set<Effect>} */
effects_ran = new Set();
@ -225,6 +228,10 @@ export class Batch {
*/
#maybe_dirty_effects = new Set();
get maybe_dirty_effects() {
return this.#maybe_dirty_effects; // TODO temporary
}
/**
* Deferred derived effects that are DIRTY
* @type {Set<Derived>}
@ -378,7 +385,7 @@ export class Batch {
}
#process() {
this.#started = true;
this.started = true;
if (DEV) {
// track all the values that were updated during this flush,
@ -399,8 +406,10 @@ export class Batch {
}
for (const e of this.#maybe_dirty_effects) {
set_signal_status(e, MAYBE_DIRTY);
this.schedule(e);
if ((e.f & DIRTY) === 0) {
set_signal_status(e, MAYBE_DIRTY);
this.schedule(e);
}
}
for (const d of this.#dirty_deriveds) {
@ -417,7 +426,7 @@ export class Batch {
// TODO does this make the similar logic in fork.commit below obsolete?
// TODO feels correct but breaks many tests
// for (const [s, [_, is_derived]] of this.current) {
// if (!is_derived) this.mark(s, DIRTY, true);
// if (!is_derived) this.mark(s, MAYBE_DIRTY, true);
// }
this.apply();
@ -822,19 +831,12 @@ export class Batch {
wv_values?.set(source, wv);
}
let batch = this.#prev;
while (batch) {
if (batch.current.has(source)) {
this.dependent.add(batch);
break;
}
batch = batch.#prev;
}
let batch = this.#next;
let is_latest_value = !this.is_fork;
batch = this.#next;
while (batch) {
if (source.f & ASYNC) {
// TODO I think this is wrong IF the async source was already written to by a later batch;
// we gotta check if it's the source is also part of the later batch.
const b = batch;
const run = () => {
if (b.mark(source, DIRTY)) {
@ -842,7 +844,7 @@ export class Batch {
}
};
if (this.is_fork) {
// this.on_fork_commit.set({}, run); // TODO
// this.on_fork_commit.set({}, run); // TODO done by mark in commit already?
} else {
queue_micro_task(run);
}
@ -871,6 +873,34 @@ export class Batch {
source.wv = wv;
}
batch = first_batch;
while (batch) {
if (batch.id < this.id && batch.current.has(source)) {
this.dependent.add(batch);
}
if (
batch.is_fork &&
is_latest_value &&
((!batch.current.has(source) && !is_derived) ||
/** @type {[any, boolean, number]} */ (batch.current.get(source))[0] !== value) &&
((source.f & ASYNC) === 0 ||
!depends_on(
source.e,
[...batch.current.keys()].filter((s) => !this.current.has(s)),
new Map()
))
) {
batch.current.set(source, [value, is_derived, wv]);
const b = batch;
queue_micro_task(() => {
if (b.mark(source, DIRTY)) {
b.flush();
}
});
}
batch = batch.#next;
}
// if (!this.is_fork) {
// let is_latest_value = true;
// batch = this.#next;
@ -989,7 +1019,7 @@ export class Batch {
);
// If not started yet or no sources to update (which is e.g. possible for the very first batch) then bail
if (!batch.#started || current.length === 0) continue;
if (!batch.started || current.length === 0) continue;
// Re-run async/block effects that depend on distinct values changed in both batches (ignoring deriveds)
var others = current.filter((source) => !this.current.has(source));
@ -1161,7 +1191,7 @@ export class Batch {
if (!is_processing && !is_flushing_sync) {
queue_micro_task(() => {
if (!batch.#started) {
if (!batch.started) {
batch.flush();
}
});
@ -1301,6 +1331,8 @@ export class Batch {
*/
export function flushSync(fn) {
var was_flushing_sync = is_flushing_sync;
var prev_previous_batch = previous_batch;
previous_batch = null;
is_flushing_sync = true;
try {
@ -1325,6 +1357,7 @@ export function flushSync(fn) {
}
} finally {
is_flushing_sync = was_flushing_sync;
previous_batch = prev_previous_batch;
}
}
@ -1708,13 +1741,14 @@ export function fork(fn) {
// but has false positives (i.e. values not updated when they should). Needs a better mechanism
// maybe current has a fourth entry, "outdated" boolean, and later batches set it for earlier ones?
// if (wv >= source.wv) {
source.v = value;
// Do not use cached wv here; real world might have executed a dependent derived and now have a later version
// TODO we need to ensure that the version bumps happen "in order", e.g. in case of source1->derived2 we need to bump S last
source.wv = increment_write_version();
// }
if (!is_derived) {
source.v = value;
source.wv = increment_write_version();
// batch.mark(source, ...) TODO re-maybe-dirty- everything?
// dirty those effects the fork did not see yet, e.g. because a later batch created new branches
batch.mark(source, DIRTY, true); // TODO probably better to only DIRTY on first non-seen derived
}

@ -122,6 +122,10 @@ export function async_derived(fn, label, location) {
var promise = /** @type {Promise<V>} */ (/** @type {unknown} */ (undefined));
var signal = source(/** @type {V} */ (UNINITIALIZED));
// Besides prod-logic this also helps in DEV to let this be printed
// as a derived when using `$inspect.trace()`
signal.f |= ASYNC;
if (DEV) signal.label = label ?? fn.toString();
// only suspend in async deriveds created on initialisation
@ -130,7 +134,8 @@ export function async_derived(fn, label, location) {
/** @type {Set<ReturnType<typeof deferred<V>>>} */
var deferreds = new Set();
async_effect(() => {
// TODO types; add to source, or special object to not have all the other objects contain another property?
signal.e = async_effect(() => {
var effect = /** @type {Effect} */ (active_effect);
if (DEV) {
@ -269,6 +274,7 @@ export function async_derived(fn, label, location) {
});
}
debugger;
internal_set(signal, value);
}
@ -284,10 +290,6 @@ export function async_derived(fn, label, location) {
}
});
// Besides prod-logic this also helps in DEV to let this be printed
// as a derived when using `$inspect.trace()`
signal.f |= ASYNC;
return new Promise((fulfil) => {
/** @param {Promise<V>} p */
function next(p) {

@ -489,7 +489,64 @@ export function update_effect(effect) {
execute_effect_teardown(effect);
var teardown = update_reaction(effect);
effect.teardown = typeof teardown === 'function' ? teardown : null;
effect.wv = write_version;
// TODO consolidate with similar logic in batch.capture()
var own_batch = previous_batch ?? current_batch;
let is_latest_value = true;
// Can be falsy inside flush_eager_effects
if (own_batch) {
is_latest_value = !own_batch.is_fork;
var batch = own_batch.next;
while (batch) {
if (
batch.started && // when flushing user effects writes sources which creates a new batch, then ignore that one
(!is_latest_value ||
// Check derived's dependencies for outdated values. We only have to check one
// level because is_dirty etc will execute the top-most deriveds first, whose result
// the later deriveds can use to make a decision ("oh this derived's value is different to what I cached")
effect.deps?.some((d) => {
var other_current = /** @type {Batch} */ (batch).current;
var own_current = /** @type {Batch} */ (own_batch).current;
// const y =
// other_current.has(d) ||
// (own_current.has(d) &&
// /** @type {[any, boolean, number]} */ (own_current.get(d))[0] !== d.v);
// if (y) debugger;
// const x =
// other_current.has(d) &&
// (!own_current.has(d) ||
// /** @type {[any, boolean, number]} */ (own_current.get(d))[0] !==
// /** @type {[any, boolean, number]} */ (other_current.get(d))[0]);
// if (x) debugger;
const z = (own_current.get(d)?.[2] ?? d.wv) != d.wv;
return z;
}))
) {
is_latest_value = false;
}
batch = batch.next;
}
}
if (is_latest_value) {
effect.wv = write_version;
} else {
// console.log('setting', effect.wv, effect, 'to', write_version, is_latest_value);
// effect.wv = write_version;
// set_signal_status(effect, MAYBE_DIRTY);
// debugger;
if (!is_latest_value) {
/** @type {Batch} */ (own_batch).stale_effects.set(effect, write_version);
var batch = /** @type {Batch} */ (own_batch).next;
while (batch) {
batch.maybe_dirty_effects.add(effect);
batch = batch.next;
}
}
// TODO add to maybe_dirty_effects in all subsequent batches here,
// removing need for other cross-batch rerun mechanisms / remove need for adding blocks to maybe_dirty?
}
// In DEV, increment versions of any sources that were written to during the effect,
// so that they are correctly marked as dirty when the effect re-runs
@ -719,9 +776,11 @@ export function get(signal) {
}
if (
// TODO correct?! I think the failure can only occur in case we see new values for the first time while flushing (render)effects
(!first_time ||
!previous_batch) /* || current_batch?.is_fork) || signal.v === UNINITIALIZED*/ &&
// TODO correct?! I thought the failure can only occur in case we see new values for the first time while flushing (render)effects,
// but it can also occur when resolving async deriveds after creating them for the first time, which can happen outside
// the effects flush phase.
(!first_time || !previous_batch) &&
// (!first_time || current_batch?.is_fork || signal.v === UNINITIALIZED) &&
batch_values?.has(signal)
) {
return batch_values.get(signal);

Loading…
Cancel
Save