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.
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.
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.
Visual
Material, Texture, EnvMap, Font.
Audio
Audio, SoundConfig, SpatializationConfig, SoundGraphSound.
Motion / animation
Animation, AnimationGraph, Motion, MotionGraph.
Scripting & ML
Script, ScriptFile, OnnxModel.
Robotics / data
MujocoScene, ParquetFile.
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::MemoryOnlyso 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::filesystemcalls, no path strings) leak into runtime paths. - New asset type checklist:
AssetTypeenum entry,Assetsubclass, serializer,AssetImporterregistration.
Sync vs. async
| Call | Returns | Behaviour |
|---|---|---|
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. |
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.
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
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.
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.