Lucky Robots Blog Open Roles

2.9 · Asset System

Loads, caches, serialises, and tracks dependencies for every kind of data the engine consumes — meshes, textures, scenes, sounds, scripts, ONNX policies. A single facade hides the difference between editing on disk and shipping in a packed bundle.

Module: Hazel/src/Hazel/Asset/ Key header: AssetManager.h AssetTypes.h Facade-only public API
Engine / Editor / Scripts callers AssetManager static facade · only entry point EditorAssetManager file-based owns AssetRegistry RuntimeAssetManager reads packed AssetPack AssetRegistry AssetHandle → AssetMetadata AssetPack serialised binary bundle edit mode play / runtime
All callers go through AssetManager; the backend is chosen by build / run mode.

Overview

Two backends sit behind one static facade. In the editor, assets live as discrete files on disk and an AssetRegistry maps stable AssetHandle IDs to AssetMetadata records. In a shipped build, the same handles resolve through an AssetPack — one serialised bundle produced by the build pipeline. Engine code stays the same in both modes because every load goes through AssetManager.

Do not reach past the facade

Never call EditorAssetManager or RuntimeAssetManager directly from engine, gameplay, or script code. Code that bypasses AssetManager works in the editor and crashes the moment it runs against an AssetPack. Treat the two backends as private implementation.

Asset types

Defined in AssetTypes.h. Adding a new one means an enum entry, an Asset subclass, a serializer (deriving AssetSerializer), and registration with AssetImporter.

Scene & geometry

Scene, Prefab, Mesh, StaticMesh, MeshSource, MeshCollider.

spatial

Visual

Material, Texture, EnvMap, Font.

renderer

Audio

Audio, SoundConfig, SpatializationConfig, SoundGraphSound.

miniaudio

Motion / animation

Animation, AnimationGraph, Motion, MotionGraph.

behaviour

Scripting & ML

Script, ScriptFile, OnnxModel.

policy

Robotics / data

MujocoScene, ParquetFile.

datasets

Conventions

  • After import, refer to an asset by its AssetHandle — never by file path. Paths only exist in the editor registry; runtime never sees them.
  • Procedural meshes, in-memory textures, and other generated content must set AssetFlag::MemoryOnly so they are not written to disk or packed.
  • All engine code must compile and behave correctly with either backend. No editor-only assumptions (no std::filesystem calls, no path strings) leak into runtime paths.
  • New asset type checklist: AssetType enum entry, Asset subclass, serializer, AssetImporter registration.

Sync vs. async

CallReturnsBehaviour
GetAsset<T>(handle) Ref<T> Synchronous. Blocks until the asset is loaded. Use when load latency does not matter (one-time setup).
GetAssetAsync<T>(handle) AsyncAssetResult<T> Returns immediately. IsReady flips to true when the background thread finishes the load. Pump from the main thread via SyncWithAssetThread.
Pump the async queue

Async results never become ready unless someone calls SyncWithAssetThread on the main thread. The application loop does this for you; long-running editor tools that spin their own loops must call it too or async loads will appear to hang forever.

Dependencies

Some assets refer to other assets — materials reference textures, scenes reference meshes and audio, animation graphs reference animations. Track the relationship with RegisterDependency(dependency, dependent). When dependency is reloaded (a texture edited on disk, a mesh re-imported), every registered dependent receives OnDependencyUpdated(handle) and can refresh itself.

Use it for hot reload

This is the mechanism that makes editor hot-reload feel instant. If your asset type has a downstream consumer (renderer cache, GPU resource, baked acceleration structure), register the dependency at import time and rebuild in OnDependencyUpdated.

Pitfalls

Path strings smell bad

A path string in runtime code is almost always wrong. Convert at import time, store the AssetHandle, and forget the path. The runtime backend has no concept of paths.

MemoryOnly or it disappears

Forget AssetFlag::MemoryOnly on a runtime-generated asset and the editor will try to write it to the project folder — or worse, the pack builder will fail because there is no source file.

Extending

The full step-by-step for adding a new asset type — enum entry, Asset subclass, serializer, importer registration, panel integration — lives in the Playbook.