Bundle bundles many assets into a single .bundle archive so a game ships a few
large downloads instead of hundreds of small files. Bundles load asynchronously,
declare dependencies on one another, ship per-platform variants (mobile
vs desktop), and can be streamed one entry at a time out of a large remote
archive with HTTP Range requests. Assets are addressed by stable keys (guids
or authored addresses) and grouped with labels, indexed by a catalog.json.
It sits under the existing loaders (GltfLoader, Texture, HDR/EXR/…): a bundle
provides the bytes, the loaders decode them as before.
import { BundleManager, BundleAssets, detectProfile } from 'taos/assets/index.js';
const bundles = new BundleManager({ profile: detectProfile(device) });
await bundles.mountCatalog('assets/catalog.json'); // fetch + parse the index
await bundles.loadLabel('startup'); // loads every bundle the label touches (+ deps)
const assets = new BundleAssets(bundles, device);
const mesh = await assets.getMesh('guid-duck'); // bytes → GltfLoader → Mesh, cachedArchive format#
A .bundle is [ entry blobs ][ gzipped-JSON TOC ][ 32-byte footer ], little-endian.
Each entry blob is compressed independently (gzip via the WHATWG
CompressionStream, the same code path in browser and Node ≥ 18) so a single
entry can be pulled and decompressed on its own. The footer at the end lets a
reader learn the file size, read the last 32 bytes, then Range-read just the TOC.
See bundle_format.ts.
Runtime#
BundleManager(bundle_manager.ts) — mount a catalog,loadBundle(id)/loadLabel(label)(resolves the dependency closure, dedups in-flight loads, reference-counts each bundle),release(id)(unloads at zero references),readBytes(key)(auto-loads the owning bundle),setProfile(...),onProgress(...).BundleCatalog— bundle list, asset→bundle map, label index, dependency topological sort (with cycle detection), and variant selection by profile.BundleSource—BufferSource(in-memory),HttpRangeSource(Range requests; transparently falls back to a full download if the server answers200).BundleAssets— typed, cached decode of bundle bytes through the existing loaders (getMesh/getTexture/getGltf/getHdrTexture/getJson).RuntimeProfile/detectProfile(device)— capability profile (maxTextureSize, BC/ASTC/ETC2 support) that drives variant selection.
Engine / scene integration#
BundleAssetResolver (bundle_resolver.ts)
implements the engine's AssetResolver, so a bundleed game resolves scene/assembly
asset references straight out of archives — no per-file fetch, no editor IPC:
import { BundleAssetResolver } from 'taos/engine/scene_runtime/index.js';
const resolver = new BundleAssetResolver(bundles, new BundleAssets(bundles, device));
instantiateScene(sceneDoc, { engine, resolver });Catalog (catalog.json)#
{
"schema": 1,
"bundles": [
{ "id": "shared", "url": "shared.bundle" },
{ "id": "art", "url": "art.bundle", "dependsOn": ["shared"],
"variants": [{ "profile": "mobile", "url": "art.mobile.bundle" }] }
],
"assets": { "guid-duck": { "bundle": "art", "labels": ["enemy"] } },
"labels": { "enemy": ["guid-duck"] }
}Building bundles#
A project declares groups in bundles.json (one group → one .bundle):
{ "schema": 1, "groups": [
{ "id": "shared", "include": { "paths": ["Content/Shared"] } },
{ "id": "art", "dependsOn": ["shared"],
"include": { "labels": ["enemy"], "paths": ["Content/Mobs"] },
"compression": "gzip" }
] }- CLI (headless / CI):
npm run build-bundles -- <projectDir>→Builds/*.bundle+Builds/catalog.json(scripts/build_bundles.mjs). - Editor: the Bundles panel edits the groups and runs the build; both the
Electron and web editors drive the shared
publishBundles(bundle_publish.ts) core.
Asset keys come from the editor's .meta guids (path-based otherwise); optional
labels in a .meta sidecar carry into the catalog. v1 variants are authored
from explicitly-listed sources; automatic texture transcode/downscale is a
follow-up.
See also#
- Sample:
bundle_streaming— builds bundles at runtime, loads by label, swaps Desktop/Mobile variants, and probes Range reads.