Skip to content

Commit ba4798c

Browse files
committed
fix(scripts): handle embedded images on headless Linux in rive-extract-schema
Patch makeRenderImage to wrap img.decode so img.la() fires via queueMicrotask after the WASM K() returns (calling it synchronously resolves the Promise with null since H hasn't been assigned yet). Also pass actual embedded bytes through CustomFileAssetLoader so fonts and images are decoded, not just marked handled.
1 parent 93c1f37 commit ba4798c

1 file changed

Lines changed: 36 additions & 5 deletions

File tree

scripts/rive-extract-schema.ts

Lines changed: 36 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,12 +60,43 @@ async function main() {
6060
const bytes = await loadBytes(source);
6161
const runtime = await RuntimeLoader.awaitInstance();
6262

63-
// Use CustomFileAssetLoader to claim all embedded assets (images, fonts) as
64-
// handled so the runtime never calls makeRenderImage. Without this, .riv files
65-
// with embedded images cause the WASM to call process.exit(0) on headless Linux
66-
// where WebGL is unavailable. We only need the schema (artboards/VMs), not images.
63+
// On headless Linux (no WebGL) the WASM image-load counter (aa.total/aa.loaded)
64+
// never reaches its target because image texture creation silently fails, so
65+
// load() never resolves. We patch makeRenderImage to wrap img.decode so that
66+
// img.la() is always called after decode — a safe no-op if WebGL already fired
67+
// it, but unblocks the Promise when WebGL is absent.
68+
const origMRI = (runtime.renderFactory as any).makeRenderImage.bind(
69+
runtime.renderFactory
70+
);
71+
(runtime.renderFactory as any).makeRenderImage = function () {
72+
const img = origMRI();
73+
if (
74+
img &&
75+
typeof (img as any).la === 'function' &&
76+
typeof (img as any).decode === 'function'
77+
) {
78+
const origDecode = (img as any).decode.bind(img);
79+
(img as any).decode = function (imgBytes: Uint8Array) {
80+
origDecode(imgBytes); // fires la() internally if WebGL succeeds
81+
// Defer la() to a microtask so it fires *after* the WASM's K() returns and
82+
// assigns H. Calling la() synchronously inside K() would resolve the Promise
83+
// with H=null (K hasn't returned yet), producing an empty file.
84+
queueMicrotask(() => (img as any).la());
85+
};
86+
}
87+
return img;
88+
};
89+
90+
// CustomFileAssetLoader handles embedded assets so the runtime resolves load().
91+
// For fonts: decode() loads font bytes (no WebGL needed).
92+
// For images: decode() triggers our patched img.decode → img.la() unblocks.
6793
const assetLoader = new (runtime as any).CustomFileAssetLoader({
68-
loadContents: () => true,
94+
loadContents: (asset: any, embeddedBytes: Uint8Array) => {
95+
if (embeddedBytes?.length && asset?.decode) {
96+
asset.decode(embeddedBytes);
97+
}
98+
return true;
99+
},
69100
});
70101

71102
const riveFile = await runtime.load(bytes, assetLoader, false);

0 commit comments

Comments
 (0)