UntoldEngine loads meshes asynchronously so scene setup does not stall the render loop. Use native .untold assets for runtime geometry. USD/USDZ files remain authoring/import inputs for the exporter, but runtime mesh loading and streaming use .untold.
setEntityMeshAsync is the primary async asset-loading API for always-resident assets:
let entity = createEntity()
setEntityMeshAsync(
entityId: entity,
filename: "robot",
withExtension: "untold"
) { success in
guard success else { return }
translateTo(entityId: entity, position: simd_float3(0, 0, 0))
}The completion Bool is a success flag:
true: the asset loaded and registered successfullyfalse: loading failed and the engine fell back to the default placeholder mesh
For ordinary public use, this API loads the asset immediately into GPU residency. It does not opt the asset into tile streaming.
Use scene readiness when your own setup performs multiple dependent mutations:
setSceneReady(false)
let entity = createEntity()
setEntityMeshAsync(entityId: entity, filename: "robot", withExtension: "untold") { success in
if success {
setEntityKinetics(entityId: entity)
translateTo(entityId: entity, position: simd_float3(0, 0, 0))
}
setSceneReady(success)
}For ordinary setEntityMeshAsync(...) and setEntityStreamScene(...) flows, the engine already uses internal loading gates. setSceneReady(...) is mainly for your own multi-step scene setup.
| Use case | API |
|---|---|
| Small asset needed immediately on the next line | setEntityMesh(...) |
| Single always-resident asset | setEntityMeshAsync(...) |
| Large streamed world | setEntityStreamScene(...) |
Use setEntityMesh when setup code needs the mesh registered before the next statement runs:
let player = createEntity()
setEntityMesh(entityId: player, filename: "player", withExtension: "untold")
translateTo(entityId: player, position: simd_float3(0, 0, 0))This is a blocking immediate load. Do not use it for large runtime assets or tile-streamed worlds.
Use setEntityMeshAsync for props, characters, gameplay objects, HUD meshes, and any asset that should stay resident but can load off the main setup path.
setEntityMeshAsync(
entityId: entity,
filename: "stadium",
withExtension: "untold"
) { success in
if success {
setEntityStaticBatchComponent(entityId: entity)
}
}Use setEntityStreamScene for geometry that should stream by camera distance:
let sceneRoot = createEntity()
setEntityName(entityId: sceneRoot, name: "dungeon")
setEntityStreamScene(entityId: sceneRoot, manifest: "dungeon", withExtension: "json") { success in
setSceneReady(success)
}Or load a remote manifest directly:
let sceneRoot = createEntity()
setEntityName(entityId: sceneRoot, name: "dungeon")
if let url = URL(string: "https://cdn.example.com/dungeon/dungeon.json") {
setEntityStreamScene(entityId: sceneRoot, url: url) { success in
setSceneReady(success)
}
}Legacy overloads —
loadTiledScene(manifest:)andloadTiledScene(url:)remain available for backwards compatibility.
This is the public streaming workflow. Do not build app-level streaming logic around StreamingComponent or enableStreaming(...); those are internal to the tile/OCC pipeline.
setEntityMeshAsync accepts a streamingPolicy parameter, but the public contract is intentionally narrow:
.immediate— default forsetEntityMeshAsync; uploads in one pass and leaves the asset GPU-resident..auto— used bysetEntityStreamScene(...)internally for tile payloads so the runtime can choose full-tile upload vs OCC stub registration..outOfCore— internal/specialized tile OCC route; do not use it as an app-level streaming API.
setEntityMeshAsync(
entityId: entity,
filename: "small_prop",
withExtension: "untold",
streamingPolicy: .immediate
).outOfCore is reserved for the engine's internal tile streaming pipeline. Passing it
directly on a standalone entity is unsupported — StreamingComponent stubs created
outside of a TileComponent hierarchy are not managed by GeometryStreamingSystem.
Use setEntityStreamScene(...) instead when you need distance-based streaming.
Task {
let isLoading = await AssetLoadingState.shared.isLoadingAny()
print("Loading: \(isLoading)")
}Task {
let count = await AssetLoadingState.shared.loadingCount()
print("Loading \(count) asset(s)")
}Task {
let (current, total) = await AssetLoadingState.shared.totalProgress()
let percentage = total > 0 ? Float(current) / Float(total) * 100.0 : 0.0
print("Progress: \(percentage)% (\(current)/\(total))")
}Task {
let summary = await AssetLoadingState.shared.loadingSummary()
print(summary)
}Task {
if let progress = await AssetLoadingState.shared.getProgress(for: entity) {
print("\(progress.filename): \(progress.currentMesh)/\(progress.totalMeshes)")
}
}.untoldis the runtime format for mesh loading and streaming.- USD/USDZ assets should be converted to
.untoldbefore runtime use. - Animation clips exported with
--animationcan be loaded as.untoldassets. setEntityStreamScene(...)automatically aligns texture streaming distances to the manifest radii and enables the full tile/HLOD/LOD/OCC streaming pipeline.