docs / core-concepts / assets

Assets

AssetManager in @g2w/core is a minimal, renderer-agnostic asset cache. Keys are opaque strings; values are whatever the injected loader returns. The manager never interprets keys itself — everything format- and browser-specific lives in the loader you inject or the adapter you use.

Load and cache

src/assets.ts
import { AssetManager } from "@g2w/core";

const textures = new AssetManager<Texture>({
  load: (url) => pixiAssetsLoad(url),
  disposeAsset: (texture) => texture.destroy(true),
});

const logo = await textures.load("logo.png");

The first load(key) invokes the loader; every later request for the same key returns the same promise — and therefore the same value. ThedisposeAsset option decides ownership (see below): with it, the manager owns its resources; without it, it merely caches caller-owned values.

Concurrent requests deduplicate

Requests made while a load is in flight share that one load. Failure is retryable: a rejected load removes its cache entry without poisoning it, so a later load(key) invokes the loader again — and loader errors are never wrapped:

concurrent loads
const [a, b] = await Promise.all([
  textures.load("logo.png"),
  textures.load("logo.png"),
]);
// one load, one shared texture instance

Textures with @g2w/renderer-pixi

createTextureAssetManager wraps PixiJS's Assets.loadbehind the core contract. It is deliberately non-destructive: textures live in PixiJS's global shared cache, where any other manager,Sprite.from, or subsystem can reach the same instance — so the manager never destroys them. dispose() releases its cache entries and leaves texture lifetime to the session (the page unload reclaims GPU and CPU memory). When your application knows nothing else uses a texture, calling PixiJS's Assets.unload(url) explicitly is your decision, not the manager's:

src/assets.ts
import { createTextureAssetManager } from "@g2w/renderer-pixi";

const assets = createTextureAssetManager();
const logo = await assets.load("assets/logo.png");

// ...later, during teardown:
await assets.dispose(); // the texture is NOT destroyed

Wire disposal into the game

src/main.ts
const assets = createTextureAssetManager();

// after game.init(): disposal joins the LIFO finalizer chain
game.addFinalizer(() => assets.dispose());

Disposal is terminal and idempotent: new loads throwAssetError immediately, in-flight loads still settle for their callers, and once the disposal promise resolves every owned value has been cleaned up exactly once — shared resources are never destroyed twice.

ScopeIntentionally unsupported: automatic unloading on scene transitions, reference counting, and resource dependency graphs. The caller owns the manager, decides when it lives and dies, and passes it to scenes explicitly.

Next steps

See Sprites & Transforms to render what you load, orarchitecture notes for the ownership policy in full.