Skip to content
PEERLESSDocs

Cross-origin isolation and threads

Threaded exports need SharedArrayBuffer. Here is how PEERLESS provides it and what it means for your assets.

When you need it

Godot threaded exports, Emscripten builds with pthreads and other code that uses SharedArrayBuffer need a cross-origin isolated page. Most games don't: start with a single-threaded export and enable threads only when your game needs them.

How PEERLESS provides it

Mark the build Threaded in the upload form. The game's origin is served with:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp
Cross-Origin-Resource-Policy: cross-origin

Isolation also requires the page around the game to be isolated. Store pages are not (they show trailers and creator media), so threaded games open in the full player page at /play/<game>, which is. Players press Play on the store page and land in the player.

What it means for your assets

Under require-corp, every cross-origin resource your game loads must opt in:

  • Send Cross-Origin-Resource-Policy: cross-origin from your asset or API host, or
  • Serve it with CORS (Access-Control-Allow-Origin) and load it with crossorigin.

Files inside your build need nothing extra.

Test locally

Serve your export with the same headers to reproduce the hosted environment. Check crossOriginIsolated in the console; it must be true.

serve.mjs
import { createServer } from "node:http";
import { readFile } from "node:fs/promises";
import { extname, join } from "node:path";

const types = { ".html": "text/html", ".js": "text/javascript", ".wasm": "application/wasm", ".pck": "application/octet-stream" };
createServer(async (request, response) => {
  const path = join("build", decodeURIComponent(new URL(request.url, "http://x").pathname.replace(/\/$/, "/index.html")));
  try {
    const body = await readFile(path);
    response.writeHead(200, {
      "Content-Type": types[extname(path)] || "application/octet-stream",
      "Cross-Origin-Opener-Policy": "same-origin",
      "Cross-Origin-Embedder-Policy": "require-corp",
    });
    response.end(body);
  } catch {
    response.writeHead(404).end();
  }
}).listen(8080);