Skip to content

Support Draco mesh decoding in Node via worker_threads#8951

Merged
mvaligursky merged 1 commit into
mainfrom
mv-draco-node
Jun 23, 2026
Merged

Support Draco mesh decoding in Node via worker_threads#8951
mvaligursky merged 1 commit into
mainfrom
mv-draco-node

Conversation

@mvaligursky

Copy link
Copy Markdown
Contributor

Fixes #8524. Draco glb loading failed in Node: initializeWorkers in draco-decoder.js unconditionally used browser-only APIs (Blob, URL.createObjectURL, the web Worker constructor), and the worker body in draco-worker.js talked to self — none of which exist in a Node worker_threads worker.

This branches on platform.environment === 'node' to use worker_threads, mirroring the existing gsplat-sorter / gsplat-sort-worker pattern already in the engine.

Changes:

  • draco-worker.js: resolve the worker scope as const myself = (typeof self !== 'undefined' && self) || require('node:worker_threads').parentPort; and use myself.postMessage / myself.addEventListener('message', …) instead of self. The decoder module factory is resolved as myself.DracoDecoderModule || (typeof module !== 'undefined' && module.exports) — the emscripten glue exposes it as a global in a browser worker but on module.exports (CommonJS) in a Node eval worker.
  • draco-decoder.js: in Node, spawn new Worker(code, { eval: true }) and listen with worker.on('message', …); the browser keeps the blob-URL path. The message handler normalizes message.data ?? message (web MessageEvent vs Node raw payload).

Notes:

  • As with the existing gsplat sorter, the Node main thread relies on a global Worker being available — consumers shim globalThis.Worker = require('node:worker_threads').Worker.
  • Out of scope: downloadScript / compileModule still require http(s)-reachable URLs in Node (local file paths won't fetch).

Verified by decoding examples/assets/models/heart_draco.glb with the modified source in both environments (identical output — indices 1080, vertices 12960, stride 24, position + normal): in Node (environment = node) and in the browser (environment = browser).

Draco glb loading failed in Node because the decoder unconditionally used
browser-only APIs (Blob, URL.createObjectURL, web Worker) and the worker body
talked to `self`. Branch on platform.environment === 'node' to spawn a
worker_threads worker and route messages, mirroring the existing gsplat sorter.

Fixes #8524
@github-actions

Copy link
Copy Markdown

Build size report

This PR changes the size of the minified bundles.

Bundle Minified Gzip Brotli
playcanvas.min.js 2261.1 KB (+0.2 KB, +0.01%) 581.7 KB (+0.1 KB, +0.01%) 452.3 KB (+0.4 KB, +0.09%)
playcanvas.min.mjs 2258.5 KB (+0.2 KB, +0.01%) 580.8 KB (+0.1 KB, +0.02%) 451.7 KB (−0.0 KB, −0.00%)

@mvaligursky mvaligursky merged commit 1c4b76f into main Jun 23, 2026
10 checks passed
@mvaligursky mvaligursky deleted the mv-draco-node branch June 23, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Draco loading fails in Node

1 participant