Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/floor-folds-verdict-upkeep.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@solidjs/signals": patch
---

Smaller core for apps without `isPending`/`latest` or optimistic lanes: the verdict engine's async-memo bookkeeping, its re-derive checks and the pass-lane seat now sit behind the engines' hooks, so a bundle that never installs them folds that code away (hello world −92 B brotli). No behavior change.
13 changes: 8 additions & 5 deletions packages/signals/src/core/async.ts
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,9 @@ export function settlePendingSource(el: Computed<any>, source: Computed<any> = e
// A19: a verdict reader holds no pending of its own (it was re-derived
// when the source went pending, `propagateStatus`), and a landing
// equal to the committed value notifies nobody — the source settling
// is its verdict changing: it runs again.
return node._config & CONFIG_VERDICT && enqueueSub(node);
// is its verdict changing: it runs again. (Verdict readers exist only
// with the verdict engine installed.)
return holdWorkHook && node._config & CONFIG_VERDICT && enqueueSub(node);
visited.add(node);
node._time = clock;
const remaining = node._x?._pendingSources?.values().next().value;
Expand Down Expand Up @@ -431,7 +432,7 @@ export function handleAsync<T>(
if (!thenable && !iterator) {
if (el._x !== null) {
el._x._inFlight = null;
el._config &= ~CONFIG_ASYNC;
if (holdWorkHook) el._config &= ~CONFIG_ASYNC;
}
// A sync landing is the first real answer for a loadingValue node.
el._loading = false;
Expand Down Expand Up @@ -469,7 +470,9 @@ export function handleAsync<T>(
ext(el)._inFlight = result as PromiseLike<T> | AsyncIterable<T>;
// D-25 (verdict.ts `holdWork`): an async memo whose answering pass opened
// no verdict window of its own (`markVerdictReader` clears the bit).
if (!(el._flags & REACTIVE_WINDOW)) el._config |= CONFIG_ASYNC;
// Kept by the verdict engine alone (`holdWorkHook`): a build without it
// folds the bit's upkeep away.
if (holdWorkHook && !(el._flags & REACTIVE_WINDOW)) el._config |= CONFIG_ASYNC;
// The question this flight answers (A18 provenance, lanes.ts): the action
// whose slice asked it; mainline is always the current question.
el._x!._q = question || MAINLINE_QUESTION;
Expand Down Expand Up @@ -1005,7 +1008,7 @@ export function propagateStatus(el: Computed<any>, status: number, error: any):
// (F-9), and asks its next question once, at the landing (#4397).
if (
sub._config & CONFIG_REDERIVE ||
(sub._config & CONFIG_VERDICT && !(pendingSource && holdWorkHook!(sub, el))) ||
(holdWorkHook && sub._config & CONFIG_VERDICT && !(pendingSource && holdWorkHook(sub, el))) ||
(status === STATUS_PENDING && link._gen !== sub._depGen)
) {
enqueueSub(sub);
Expand Down
52 changes: 28 additions & 24 deletions packages/signals/src/core/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ import {
laneStageHook,
laneSeamsHook,
laneServedHook,
holdWorkHook,
laneWriteHook,
observeFlightHook,
wireExternalSourceHook
Expand Down Expand Up @@ -346,27 +347,27 @@ export function recompute(el: Computed<any>, create: boolean = false): void {
// Restored at the end, after this pass's staging and runs have been
// routed.
// Lanes exist only with the lane engine installed (`laneStageHook`): a
// bundle without it seats every pass on mainline, and folds the seat away.
// bundle without it seats every pass on mainline (`passLane` is never set)
// and folds the seat, and its restores, away.
const prevLane = passLane;
// The mount a first pass belongs to: its creator's lane, or the one its
// creator's own mount came from — a render effect a verdict lane's mount's
// content creates is that mount's too (#4166). Lanes only, like the seat.
const prevMount = laneRoot;
const mount = laneStageHook && (laneRoot = create && (prevLane || prevMount));
setPassLane(
!laneStageHook
? null
: (el._flags & REACTIVE_LANE_DIRTY ||
(el._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) &&
el._x?._transaction?._lane
if (laneStageHook)
setPassLane(
(el._flags & REACTIVE_LANE_DIRTY ||
(el._config & (CONFIG_OVERRIDE | CONFIG_GUESS)) === CONFIG_OVERRIDE) &&
el._x?._transaction?._lane
? el._x._transaction
: // Loose `!=`: false with no creator lane (undefined == null).
create &&
prevLane?._parent!._verdict != prevLane &&
(creatorPass(context)?._flags as number) & REACTIVE_RECOMPUTING_DEPS
? prevLane
: null
);
);
// Attribution hook: fired before this run touches the dep list — `_deps`
// still holds the previous run's links (the subscriptions that could have
// triggered this run, and the baseline for the engine's subscription diff).
Expand Down Expand Up @@ -560,7 +561,7 @@ export function recompute(el: Computed<any>, create: boolean = false): void {
if (!selfRegistered && !isAsyncResult) {
if (el._x !== null) {
el._x._inFlight = null;
el._config &= ~CONFIG_ASYNC;
if (holdWorkHook) el._config &= ~CONFIG_ASYNC;
}
// A sync (non-object) return is the first real answer; async-shaped
// results clear inside handleAsync at their own landing points, and a
Expand All @@ -575,6 +576,10 @@ export function recompute(el: Computed<any>, create: boolean = false): void {
if (el._statusFlags !== 0 || el._x !== null) clearStatus(el, create);
} catch (e) {
const notReady = e instanceof NotReadyError;
// An error answers, and so does a loading window's unready read (D-25,
// verdict.ts `holdWork`): no flight is its own. A pass left pending
// keeps the last answer's. (Kept by the verdict engine alone.)
if (holdWorkHook && (!notReady || el._loading)) el._config &= ~CONFIG_ASYNC;
if (notReady && el._loading) {
// Loading window with an unready sync dependency: register for the
// source's settle (the settlePendingSource walk runs off
Expand All @@ -583,11 +588,8 @@ export function recompute(el: Computed<any>, create: boolean = false): void {
// keeps serving. If the
// node is currently errored the error stays the answer until this
// retry can actually run.
el._config &= ~CONFIG_ASYNC;
parkLoadingWindow(el, e as NotReadyError);
} else {
// An error answers (D-25, verdict.ts `holdWork`): no flight is its own.
if (!notReady) el._config &= ~CONFIG_ASYNC;
// D-41: a pass of a held node, or one that read a staging of this
// flush, stages its error (`stageError`).
if (notReady || !(stagedErr = stageError(el, e, true))) {
Expand Down Expand Up @@ -642,7 +644,9 @@ export function recompute(el: Computed<any>, create: boolean = false): void {
// that does (`read`): a pass that did neither (REACTIVE_PROBED, the same
// bit) is an ordinary derivation again. Its async posture is the
// verdict engine's to keep (CONFIG_ASYNC, verdict.ts `holdWork`, D-25).
el._config = (el._config & ~CONFIG_VERDICT) | (el._flags & REACTIVE_PROBED);
// (Only the verdict engine probes: without it the line folds away.)
if (observeFlightHook)
el._config = (el._config & ~CONFIG_VERDICT) | (el._flags & REACTIVE_PROBED);
// REACTIVE_DISPOSED survives too (#3621): the pass may have disposed its
// own owner (a memo calling its root's `dispose()`, a cleanup doing so
// #3601/#3606), and `disposeChildren` set the flag on this node
Expand Down Expand Up @@ -684,7 +688,7 @@ export function recompute(el: Computed<any>, create: boolean = false): void {
if (el._x !== null) el._x._inFlight = null;
if (__OBSERVE__ && attrHooks !== null)
attrHooks.recomputeEnd(el, create, false, false, false, false);
setPassLane(prevLane);
if (laneStageHook) setPassLane(prevLane);
return;
}
// The lane this pass is work of, if any: its seat (the node's, its
Expand Down Expand Up @@ -960,7 +964,7 @@ export function recompute(el: Computed<any>, create: boolean = false): void {
// Lane work is the lane's to reveal (its seam), not this flush's commit.
if (lane === null) queuePendingNode(el);
}
setPassLane(prevLane);
if (laneStageHook) setPassLane(prevLane);
// Missed-wake reschedule (see the finally above): values this pass read
// before the nested commit are stale, so run again now that the heap will
// accept the node. Equality gates stop same-value landings from cascading,
Expand Down Expand Up @@ -1989,7 +1993,9 @@ export function read<T>(el: Signal<T> | Computed<T>): T {
// it probed inline: it reads held values as the screen and shows the
// verdict now. Not an effect: a render effect is a frame reader
// already, and a user effect's posture is its own reads'.
if (!(c as any)._type) (c as Computed<any>)._flags |= owner._config & CONFIG_VERDICT;
// (A verdict memo exists only with the verdict engine installed.)
if (observeFlightHook && !(c as any)._type)
(c as Computed<any>)._flags |= owner._config & CONFIG_VERDICT;
}
}
// L2, as in the fast block above (tracked or not). A node born into the
Expand Down Expand Up @@ -2052,23 +2058,21 @@ export function read<T>(el: Signal<T> | Computed<T>): T {
// — unless the flight committed beneath inputs already on screen
// (`observeFlight`), or the reader was born into the flight's transaction
// (#4010): it has no screen, and waits on the flight with its frame.
// (A lane, and a verdict reader, exist only with their engines installed —
// the verdict engine imports the lane engine: without it the test folds.)
if (
laneStageHook &&
owner._statusFlags & STATUS_PENDING &&
!committed &&
!(el._config & CONFIG_OVERRIDE) &&
!(owner._statusFlags & STATUS_UNINITIALIZED)
) {
// A reader with no committed value yet sees the flight pending: a
// boundary a lane mounts shows its fallback (A29's boundary exemption).
// (A lane, and a verdict reader, exist only with their engines installed.)
if (
laneStageHook &&
passLane !== null &&
!((c as Computed<any> | null)?._statusFlags! & STATUS_UNINITIALIZED)
)
if (passLane !== null && !((c as Computed<any> | null)?._statusFlags! & STATUS_UNINITIALIZED))
committed = true;
else if (observeFlightHook && c !== null && c._config & CONFIG_VERDICT)
committed = observeFlightHook(c as Computed<any>, owner);
else if (c !== null && c._config & CONFIG_VERDICT)
committed = observeFlightHook!(c as Computed<any>, owner);
}
if (owner._statusFlags & STATUS_PENDING && !committed) {
// A reader landing on a pending node throws; an untracked read of an
Expand Down
2 changes: 1 addition & 1 deletion packages/signals/src/core/scheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1217,7 +1217,7 @@ export function reruns(u: Transaction): unknown {
(s?._statusFlags ?? STATUS_ERROR) & STATUS_ERROR)
)
continue;
if (r._flags & REACTIVE_FRAME_READ || r._config & CONFIG_VERDICT) {
if (r._flags & REACTIVE_FRAME_READ || (observeFlightHook && r._config & CONFIG_VERDICT)) {
frame ||= (r as any)._type;
// A lane's reveal leaves the mark to the transaction: a reader that
// also read a held node as committed (`staleReader`) still observes
Expand Down
Loading
Loading