Switch the background effect with ObservableScope.reconcile

- The pipeline's switches run through reconcile, which already runs them
  one at a time and skips those overtaken, in place of a queue of our own.
- A failed switch is logged and the next still runs, now tested.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
fkwp
2026-10-08 12:54:54 +02:00
co-authored by Claude Opus 5.5
parent 32e7c407c3
commit de2b98251c
2 changed files with 30 additions and 34 deletions
+8
View File
@@ -113,6 +113,14 @@ describe("background effects", () => {
]); ]);
}); });
it("keeps switching after a switch fails", async () => {
vi.mocked(fake.pipeline.switchTo).mockRejectedValueOnce(new Error("lost"));
build();
await blur(true);
await blur(false);
expect(fake.switches).toEqual([{ mode: "disabled" }]);
});
it("switches one at a time, skipping those overtaken", async () => { it("switches one at a time, skipping those overtaken", async () => {
const finished = fake.holdNext(); const finished = fake.holdNext();
build(); build();
+22 -34
View File
@@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE in the repository root for full details. Please see LICENSE in the repository root for full details.
*/ */
import { combineLatest, distinctUntilChanged, filter, map, scan } from "rxjs"; import { combineLatest, distinctUntilChanged, map, scan } from "rxjs";
import { import {
type BackgroundProcessorWrapper, type BackgroundProcessorWrapper,
type SwitchBackgroundProcessorOptions, type SwitchBackgroundProcessorOptions,
@@ -53,41 +53,29 @@ export function createBackgroundEffects(
), ),
); );
const switchTo = oneSwitchAtATime(pipeline); const switchOptions$ = scope.behavior<
combineLatest([state$, blur$]) SwitchBackgroundProcessorOptions | undefined
.pipe( >(
filter(([{ processor }]) => processor !== undefined), combineLatest([state$, blur$]).pipe(
map(([, blur]): SwitchBackgroundProcessorOptions => map(([{ processor }, blur]) =>
blur ? { mode: "background-blur", blurRadius } : { mode: "disabled" }, processor === undefined
? undefined
: blur
? { mode: "background-blur", blurRadius }
: { mode: "disabled" },
), ),
distinctUntilChanged(deepCompare), distinctUntilChanged(deepCompare),
scope.bind(), ),
) );
.subscribe((options) => { // In turn: a picture's switch ends once it loads, so an earlier one could land last.
switchTo(options).catch((e) => scope.reconcile(switchOptions$, async (options) => {
logger.warn("Failed to switch background effect", e), if (options === undefined) return;
); try {
}); await pipeline.switchTo(options);
} catch (e) {
logger.warn("Failed to switch background effect", e);
}
});
return { state$ }; return { state$ };
} }
/**
* Switches the pipeline one choice at a time, skipping those overtaken while
* they waited. A switch to a picture ends only once it has loaded, so a
* slower, earlier choice would otherwise land after a later one.
*/
function oneSwitchAtATime(
pipeline: BackgroundProcessorWrapper,
): (options: SwitchBackgroundProcessorOptions) => Promise<void> {
let latest: SwitchBackgroundProcessorOptions | undefined;
let queue = Promise.resolve();
return async (options) => {
latest = options;
const turn = queue.then(async () => {
if (options === latest) await pipeline.switchTo(options);
});
queue = turn.catch(() => {});
return turn;
};
}