Skip to content

Instantly share code, notes, and snippets.

@drewolbrich
Last active March 11, 2024 20:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save drewolbrich/b058b65c6d3e71f7ac611d9a505902e5 to your computer and use it in GitHub Desktop.
Save drewolbrich/b058b65c6d3e71f7ac611d9a505902e5 to your computer and use it in GitHub Desktop.
A wrapper around iOS Entity/loadAsync(named:in:) and visionOS Entity(named:in:) async that works on both platforms
//
// Entity+LoadFileAsync.swift
//
// Created by Drew Olbrich on 8/4/23.
//
import Foundation
import RealityKit
import Combine
extension Entity {
// The RealityKit visionOS `Entity(named:in:) async` initializer isn't available
// in iOS 17, so we implement our own replacement on iOS and wrap both implementations
// in `Entity/loadFileAsync(named:in:)`.
//
// Although visionOS supports `Entity/loadAsync(named:in:)`, it is deprecated and
// generates a compiler warning.
//
// Maybe in the future year 2026 we won't need this, assuming iOS 18 adds support for
// `Entity(named:in:) async` and we've decided to drop support for iOS 17.
#if os(visionOS)
/// Loads an entity from a file in a bundle asynchronously.
///
/// Unlike `Entity(named:in:) async` or `Entity.loadAsync(named:in:)`, this method
/// works on both iOS and visionOS.
///
/// If `flattened` is true, the entity hierarchy in the file will be flattened into
/// a single `ModelEntity`.
static func loadFileAsync(named name: String, in bundle: Bundle? = nil, flattened: Bool) async throws -> Entity {
if flattened {
return try await ModelEntity(named: name, in: bundle)
} else {
return try await Entity(named: name, in: bundle)
}
}
#else // !os(visionOS)
/// Loads an entity from a file in a bundle asynchronously.
///
/// Unlike `Entity(named:in:) async` or `Entity.loadAsync(named:in:)`, this method
/// works on both iOS and visionOS.
///
/// If `flattened` is true, the entity hierarchy in the file will be flattened into
/// a single `ModelEntity`.
static func loadFileAsync(named name: String, in bundle: Bundle? = nil, flattened: Bool) async throws -> Entity {
return try await withCheckedThrowingContinuation { continuation in
func loadAsync<T: Entity>(with loadRequest: LoadRequest<T>) {
var cancellable: AnyCancellable?
cancellable = loadRequest.sink(receiveCompletion: { completion in
switch completion {
case .finished:
break
case .failure(let error):
continuation.resume(throwing: error)
}
cancellable?.cancel()
}, receiveValue: { entity in
continuation.resume(returning: entity)
cancellable?.cancel()
})
}
if flattened {
loadAsync(with: Entity.loadModelAsync(named: name, in: bundle))
} else {
loadAsync(with: Entity.loadAsync(named: name, in: bundle))
}
}
}
#endif // !os(visionOS)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment