fix(dictionary): stop character dictionary IO from blocking the main process

Generating and importing a large character dictionary froze the whole
app long enough for the compositor to raise its application-not-
responding dialog over the player. Multi-hundred-MB snapshot JSONs and
the merged archive were read, written, and zipped synchronously on the
main process, and the character image / name-candidate caches re-read
every cached snapshot synchronously inside a lookup whenever the
snapshot directory changed.

- snapshot reads/writes are async; writes stream in slices and rename
  into place so a crash or concurrent writer cannot tear a snapshot
- buildDictionaryZip yields between ~8MB slices and CRC32 uses the
  native zlib implementation
- the image and name-candidate lookup caches rebuild in the background
  and serve the previous index while the rebuild runs

Worst main-thread stall over a 1.4GB snapshot set drops from 8s+ to
under 700ms.
This commit is contained in:
2026-08-17 01:45:54 -07:00
parent ec7f0345b0
commit 1228ebe622
12 changed files with 361 additions and 151 deletions
+4 -4
View File
@@ -1,5 +1,5 @@
import * as path from 'path';
import { readStoredZipFirstFile, writeStoredZip } from '../../shared/stored-zip';
import { readStoredZipFirstFile, writeStoredZipAsync } from '../../shared/stored-zip';
import { ensureDir } from './fs-utils';
import type { CharacterDictionarySnapshotImage, CharacterDictionaryTermEntry } from './types';
@@ -48,14 +48,14 @@ export function readDictionaryZipRevision(zipPath: string): string | null {
}
}
export function buildDictionaryZip(
export async function buildDictionaryZip(
outputPath: string,
dictionaryTitle: string,
description: string,
revision: string,
termEntries: CharacterDictionaryTermEntry[],
images: CharacterDictionarySnapshotImage[],
): { zipPath: string; entryCount: number } {
): Promise<{ zipPath: string; entryCount: number }> {
ensureDir(path.dirname(outputPath));
function* zipFiles(): Iterable<{ name: string; data: Buffer }> {
@@ -87,6 +87,6 @@ export function buildDictionaryZip(
}
}
writeStoredZip(outputPath, zipFiles());
await writeStoredZipAsync(outputPath, zipFiles());
return { zipPath: outputPath, entryCount: termEntries.length };
}