UntoldEngine supports two batching modes in practice:
| Mode | Use for |
|---|---|
| Manual batch generation | Always-resident static content |
| Runtime cell-based batching | Tiled streaming scenes |
Mark loaded entities as static, enable batching, then build the initial artifacts.
let cube1 = createEntity()
setEntityMesh(entityId: cube1, filename: "cube", withExtension: "untold")
translateTo(entityId: cube1, position: simd_float3(0, 0, 0))
setEntityStaticBatchComponent(entityId: cube1)
let cube2 = createEntity()
setEntityMesh(entityId: cube2, filename: "cube", withExtension: "untold")
translateTo(entityId: cube2, position: simd_float3(2, 0, 0))
setEntityStaticBatchComponent(entityId: cube2)
enableBatching(true)
generateBatches()For async loading, mark entities static in the completion block:
let building = createEntity()
setEntityMeshAsync(entityId: building, filename: "office_building", withExtension: "untold") { success in
guard success else { return }
setEntityStaticBatchComponent(entityId: building)
enableBatching(true)
generateBatches()
}For tiled scenes, the flow is different:
let sceneRoot = createEntity()
setEntityName(entityId: sceneRoot, name: "city")
setEntityStreamScene(entityId: sceneRoot, manifest: "city", withExtension: "json") { success in
setSceneReady(success)
}In this mode:
registerTiledScene(...)enables batching automatically- full-load tiles notify batching through
notifyTileEntitiesResident(_:) - OCC sub-mesh uploads join batching incrementally through normal residency events
- per-tile LOD and HLOD representations can also participate when enabled
You do not call generateBatches() every time a tile loads. The batching system rebuilds dirty cells incrementally based on residency changes.
For tiled/streamed scenes, the engine manages static batching automatically. When a tile finishes loading, the engine assigns a StaticBatchComponent to all of its entities and schedules an incremental batch rebuild for only the spatial cells affected by that tile. This happens internally on a background queue via the engine's tick() loop.
Do not call generateBatches() for streamed scenes. That function performs a full global rebuild — it queries every entity in the scene simultaneously, merges entities from different tiles into shared batch groups, and allocates all GPU buffers synchronously on the render thread. This overrides the engine's incremental system and causes a noticeable stall.
For streamed scenes, only call enableBatching(true) after the scene loads. The engine handles the rest:
setEntityStreamScene(entityId: sceneRoot, manifest: "city", withExtension: "json") { success in
enableBatching(true)
setSceneReady(success)
}For non-streamed scenes (single .untold), call setEntityStaticBatchComponent, generateBatches(), and enableBatching(true) as normal. The same applies to any operation that mutates material state (color, opacity) — wrap it with enableBatching(false) before and generateBatches() + enableBatching(true) after, but only for non-streamed scenes:
// Non-streamed only — do not use this pattern in tiled/streamed scenes
enableBatching(false)
setEntityColor(entityId: prop, color: simd_float4(1, 0, 0, 1))
generateBatches()
enableBatching(true)Marks an entity hierarchy as eligible for batching.
setEntityStaticBatchComponent(entityId: entity)Removes static batching tags from the entity hierarchy.
removeEntityStaticBatchComponent(entityId: entity)Globally enables or disables runtime batching.
enableBatching(true)Builds batch artifacts for the currently marked static entities. This is mainly for always-resident/manual workflows.
generateBatches()Clears all generated batch artifacts.
clearSceneBatches()- environment geometry
- buildings and structures
- terrain chunks
- furniture and static props
- characters and NPCs
- vehicles
- projectiles
- animated or skinned meshes
- objects that move frequently
- The batching system is now cell-based and visibility-gated.
- Tile streaming and batching are tightly integrated; residency events are no longer the old per-entity event storm for full-load tiles.
TileLODTagComponentlets batching treat per-tile LODs and HLODs as distinct LOD groups even though they are not entity-levelLODComponentassets.
For architectural details, see Batching System.