Skip to content

Commit fd68bc9

Browse files
committed
Reuse resource importers
1 parent 90c82bf commit fd68bc9

18 files changed

+546
-367
lines changed

Sources/GateEngine/Helpers/TextureAtlas.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,8 +120,9 @@ public final class TextureAtlasBuilder {
120120
}
121121

122122
public func insertTexture(withPath unresolvedPath: String) throws {
123-
let data = try Platform.current.synchronousLoadResource(from: unresolvedPath)
124-
let png = try PNGImporter().process(data: data, size: nil, options: .none)
123+
let importer = PNGImporter()
124+
try importer.synchronousPrepareToImportResourceFrom(path: unresolvedPath)
125+
let png = try importer.loadTexture(options: .none)
125126
let width = Int(png.size.width)
126127
let height = Int(png.size.height)
127128
let coord = searchGrid.firstUnoccupiedFor(

Sources/GateEngine/Resources/Geometry/Geometry.swift

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -131,12 +131,8 @@ extension Geometry: Equatable, Hashable {
131131

132132
// MARK: - Resource Manager
133133

134-
public protocol GeometryImporter: AnyObject {
135-
init()
136-
137-
func loadData(path: String, options: GeometryImporterOptions) async throws -> RawGeometry
138-
139-
static func canProcessFile(_ file: URL) -> Bool
134+
public protocol GeometryImporter: ResourceImporter {
135+
func loadGeometry(options: GeometryImporterOptions) async throws -> RawGeometry
140136
}
141137

142138
public struct GeometryImporterOptions: Equatable, Hashable, Sendable {
@@ -177,10 +173,10 @@ extension ResourceManager {
177173
}
178174
}
179175

180-
func geometryImporterForFile(_ file: URL) -> (any GeometryImporter)? {
176+
func geometryImporterForPath(_ path: String) async throws -> (any GeometryImporter)? {
181177
for type in self.importers.geometryImporters {
182-
if type.canProcessFile(file) {
183-
return type.init()
178+
if type.canProcessFile(path) {
179+
return try await self.importers.getImporter(path: path, type: type)
184180
}
185181
}
186182
return nil
@@ -193,17 +189,14 @@ extension RawGeometry {
193189
try await self.init(path: path.value, options: options)
194190
}
195191
public init(path: String, options: GeometryImporterOptions = .none) async throws {
196-
let file = URL(fileURLWithPath: path)
197192
guard
198-
let importer: any GeometryImporter = await Game.shared.resourceManager.geometryImporterForFile(
199-
file
200-
)
193+
let importer: any GeometryImporter = try await Game.shared.resourceManager.geometryImporterForPath(path)
201194
else {
202-
throw GateEngineError.failedToLoad("No importer for \(file.pathExtension).")
195+
throw GateEngineError.failedToLoad("No importer for \(URL(fileURLWithPath: path).pathExtension).")
203196
}
204197

205198
do {
206-
self = try await importer.loadData(path: path, options: options)
199+
self = try await importer.loadGeometry(options: options)
207200
} catch {
208201
throw GateEngineError(error)
209202
}

Sources/GateEngine/Resources/Geometry/Lines.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,14 @@ extension RawLines {
6868
try await self.init(path: path.value, options: options)
6969
}
7070
public init(path: String, options: GeometryImporterOptions = .none) async throws {
71-
let file = URL(fileURLWithPath: path)
7271
guard
73-
let importer: any GeometryImporter = await Game.shared.resourceManager.geometryImporterForFile(
74-
file
75-
)
72+
let importer: any GeometryImporter = try await Game.shared.resourceManager.geometryImporterForPath(path)
7673
else {
77-
throw GateEngineError.failedToLoad("No importer for \(file.pathExtension).")
74+
throw GateEngineError.failedToLoad("No importer for \(URL(fileURLWithPath: path).pathExtension).")
7875
}
7976

8077
do {
81-
self = RawLines(wireframeFrom: try await importer.loadData(path: path, options: options).generateTriangles())
78+
self = RawLines(wireframeFrom: try await importer.loadGeometry(options: options).generateTriangles())
8279
} catch {
8380
throw GateEngineError(error)
8481
}

Sources/GateEngine/Resources/Geometry/Points.swift

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,14 @@ extension RawPoints {
6868
try await self.init(path: path.value, options: options)
6969
}
7070
public init(path: String, options: GeometryImporterOptions = .none) async throws {
71-
let file = URL(fileURLWithPath: path)
7271
guard
73-
let importer: any GeometryImporter = await Game.shared.resourceManager.geometryImporterForFile(
74-
file
75-
)
72+
let importer: any GeometryImporter = try await Game.shared.resourceManager.geometryImporterForPath(path)
7673
else {
77-
throw GateEngineError.failedToLoad("No importer for \(file.pathExtension).")
74+
throw GateEngineError.failedToLoad("No importer for \(URL(fileURLWithPath: path).pathExtension).")
7875
}
7976

8077
do {
81-
self = RawPoints(pointCloudFrom: try await importer.loadData(path: path, options: options).generateTriangles())
78+
self = RawPoints(pointCloudFrom: try await importer.loadGeometry(options: options).generateTriangles())
8279
} catch {
8380
throw GateEngineError(error)
8481
}

Sources/GateEngine/Resources/Import & Export/Importers/ApplePlatformImageImporter.swift

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,63 @@
99
import ImageIO
1010
import CoreImage
1111
import CoreServices
12+
import GameMath
1213

1314
public final class ApplePlatformImageImporter: TextureImporter {
15+
var data: Data! = nil
16+
var size: Size2! = nil
1417
public required init() {}
15-
16-
public func process(data: Data, size: Size2?, options: TextureImporterOptions) throws -> (
17-
data: Data, size: Size2
18-
) {
19-
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else {
20-
throw GateEngineError.generic("Failed to decode image source.")
18+
19+
public func synchronousPrepareToImportResourceFrom(path: String) throws(GateEngineError) {
20+
do {
21+
let data = try Platform.current.synchronousLoadResource(from: path)
22+
try self.populateFromData(data)
23+
}catch{
24+
throw GateEngineError(error)
2125
}
22-
guard let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
23-
throw GateEngineError.generic("Failed to decode subimage zero.")
26+
}
27+
28+
public func prepareToImportResourceFrom(path: String) async throws(GateEngineError) {
29+
do {
30+
let data = try await Game.shared.platform.loadResource(from: path)
31+
try self.populateFromData(data)
32+
}catch{
33+
throw GateEngineError(error)
2434
}
25-
let size = Size2(Float(image.width), Float(image.height))
26-
guard let data = image.dataProvider?.data as? Data else {
27-
throw GateEngineError.generic("Failed to decode data.")
35+
}
36+
37+
func populateFromData(_ data: Data) throws(GateEngineError) {
38+
do {
39+
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else {
40+
throw GateEngineError.generic("Failed to decode image source.")
41+
}
42+
guard let image = CGImageSourceCreateImageAtIndex(imageSource, 0, nil) else {
43+
throw GateEngineError.generic("Failed to decode subimage zero.")
44+
}
45+
self.size = Size2(Float(image.width), Float(image.height))
46+
guard let data = image.dataProvider?.data as? Data else {
47+
throw GateEngineError.generic("Failed to decode data.")
48+
}
49+
self.data = data
50+
}catch{
51+
throw GateEngineError(error)
2852
}
53+
}
54+
55+
public func loadTexture(options: TextureImporterOptions) async throws -> (data: Data, size: Size2) {
2956
return (data, size)
3057
}
3158

32-
public static func canProcessFile(_ file: URL) -> Bool {
59+
public static func canProcessFile(_ path: String) -> Bool {
60+
let pathExtension = URL(fileURLWithPath: path).pathExtension
61+
guard pathExtension.isEmpty == false else {return false}
3362
guard let identifiers = (CGImageSourceCopyTypeIdentifiers() as? [CFString]) else {
3463
return false
3564
}
3665
guard
3766
let uttype = UTTypeCreatePreferredIdentifierForTag(
3867
kUTTagClassFilenameExtension,
39-
file.pathExtension as CFString,
68+
pathExtension as CFString,
4069
kUTTypeImage
4170
)?.takeRetainedValue()
4271
else { return false }

Sources/GateEngine/Resources/Import & Export/Importers/ApplePlatformModelImporter.swift

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,22 @@ public final class ApplePlatformModelImporter: GeometryImporter {
137137
return indices
138138
}
139139

140-
public func loadData(path: String, options: GeometryImporterOptions) async throws -> RawGeometry {
141-
let asset = MDLAsset(url: URL(fileURLWithPath: path))
142-
140+
var asset: MDLAsset! = nil
141+
public func synchronousPrepareToImportResourceFrom(path: String) throws(GateEngineError) {
142+
guard let path = Platform.current.synchronousLocateResource(from: path) else {throw .failedToLocate}
143+
self.asset = MDLAsset(url: URL(fileURLWithPath: path))
144+
}
145+
public func prepareToImportResourceFrom(path: String) async throws(GateEngineError) {
146+
guard let path = await Game.shared.platform.locateResource(from: path) else {throw .failedToLocate}
147+
self.asset = await withCheckedContinuation { continuation in
148+
Task.detached {
149+
let asset = MDLAsset(url: URL(fileURLWithPath: path))
150+
continuation.resume(returning: asset)
151+
}
152+
}
153+
}
154+
155+
public func loadGeometry(options: GeometryImporterOptions) async throws -> RawGeometry {
143156
for meshIndex in 0 ..< asset.count {
144157
guard let mesh = asset.object(at: meshIndex) as? MDLMesh else {
145158
throw GateEngineError.failedToDecode("mesh[\(meshIndex)] is not a MDLMesh instance.")
@@ -165,8 +178,8 @@ public final class ApplePlatformModelImporter: GeometryImporter {
165178
throw GateEngineError.failedToDecode("Failed to locate model.")
166179
}
167180

168-
public static func canProcessFile(_ file: URL) -> Bool {
169-
return MDLAsset.canImportFileExtension(file.pathExtension)
181+
public static func canProcessFile(_ path: String) -> Bool {
182+
return MDLAsset.canImportFileExtension(URL(fileURLWithPath: path).pathExtension)
170183
}
171184
}
172185

Sources/GateEngine/Resources/Import & Export/Importers/GLTransmissionFormat.swift

Lines changed: 35 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -406,9 +406,31 @@ private class GLTF: Decodable {
406406
}
407407
}
408408

409-
public final class GLTransmissionFormat {
409+
public final class GLTransmissionFormat: ResourceImporter {
410+
fileprivate var gltf: GLTF! = nil
410411
required public init() {}
411-
412+
413+
public func synchronousPrepareToImportResourceFrom(path: String) throws(GateEngineError) {
414+
guard let path = Platform.current.synchronousLocateResource(from: path) else {throw .failedToLocate}
415+
let baseURL = URL(fileURLWithPath: path).deletingLastPathComponent()
416+
do {
417+
let data = try Platform.current.synchronousLoadResource(from: path)
418+
self.gltf = try gltf(from: data, baseURL: baseURL)
419+
}catch{
420+
throw GateEngineError(error)
421+
}
422+
}
423+
public func prepareToImportResourceFrom(path: String) async throws(GateEngineError) {
424+
guard let path = await Game.shared.platform.locateResource(from: path) else {throw .failedToLocate}
425+
let baseURL = URL(fileURLWithPath: path).deletingLastPathComponent()
426+
do {
427+
let data = try await Game.shared.platform.loadResource(from: path)
428+
self.gltf = try gltf(from: data, baseURL: baseURL)
429+
}catch{
430+
throw GateEngineError(error)
431+
}
432+
}
433+
412434
fileprivate func gltf(from data: Data, baseURL: URL) throws -> GLTF {
413435
var jsonData: Data = data
414436
var bufferData: Data? = nil
@@ -424,29 +446,23 @@ public final class GLTransmissionFormat {
424446
gltf.cachedBuffers[0] = bufferData
425447
return gltf
426448
}
427-
428-
public static func canProcessFile(_ file: URL) -> Bool {
429-
let fileType = file.pathExtension
430-
if fileType.caseInsensitiveCompare("glb") == .orderedSame {
431-
return true
432-
}
433-
if fileType.caseInsensitiveCompare("gltf") == .orderedSame {
434-
return true
435-
}
436-
return false
437-
}
438449

439450
public static func supportedFileExtensions() -> [String] {
440451
return ["gltf", "glb"]
441452
}
453+
454+
public func currentFileContainsMutipleResources() -> Bool {
455+
return [
456+
gltf.meshes?.count,
457+
gltf.animations?.count,
458+
gltf.skins?.count,
459+
].compactMap({$0}).reduce(0, +) > 1
460+
}
461+
442462
}
443463

444464
extension GLTransmissionFormat: GeometryImporter {
445-
public func loadData(path: String, options: GeometryImporterOptions) async throws -> RawGeometry {
446-
let baseURL = URL(string: path)!.deletingLastPathComponent()
447-
let data = try await Game.shared.platform.loadResource(from: path)
448-
let gltf = try gltf(from: data, baseURL: baseURL)
449-
465+
public func loadGeometry(options: GeometryImporterOptions) async throws -> RawGeometry {
450466
guard gltf.meshes != nil else {throw GateEngineError.failedToDecode("File contains no geometry.")}
451467

452468
var mesh: GLTF.Mesh? = nil
@@ -611,10 +627,7 @@ extension GLTransmissionFormat: SkinImporter {
611627
})
612628
}
613629

614-
public func loadData(path: String, options: SkinImporterOptions) async throws -> Skin {
615-
let baseURL = URL(string: path)!.deletingLastPathComponent()
616-
let data = try await Game.shared.platform.loadResource(from: path)
617-
let gltf = try gltf(from: data, baseURL: baseURL)
630+
public func loadSkin(options: SkinImporterOptions) async throws(GateEngineError) -> Skin {
618631
guard let skins = gltf.skins, skins.isEmpty == false else {
619632
throw GateEngineError.failedToDecode("File contains no skins.")
620633
}

Sources/GateEngine/Resources/Import & Export/Importers/PNGImporter.swift

Lines changed: 33 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,44 @@
55
* http://stregasgate.com
66
*/
77

8+
import GameMath
9+
810
public final class PNGImporter: TextureImporter {
11+
var data: Data! = nil
12+
var size: Size2! = nil
913
public required init() {}
14+
15+
public func currentFileContainsMutipleResources() -> Bool {
16+
return false
17+
}
18+
19+
public func synchronousPrepareToImportResourceFrom(path: String) throws(GateEngineError) {
20+
do {
21+
let data = try Platform.current.synchronousLoadResource(from: path)
22+
let png = try PNGDecoder().decode(data)
23+
self.data = png.data
24+
self.size = Size2(Float(png.width), Float(png.height))
25+
}catch{
26+
throw GateEngineError(error)
27+
}
28+
}
29+
public func prepareToImportResourceFrom(path: String) async throws(GateEngineError) {
30+
do {
31+
let data = try await Game.shared.platform.loadResource(from: path)
32+
let png = try PNGDecoder().decode(data)
33+
self.data = png.data
34+
self.size = Size2(Float(png.width), Float(png.height))
35+
}catch{
36+
throw GateEngineError(error)
37+
}
38+
}
1039

11-
public func process(data: Data, size: Size2?, options: TextureImporterOptions) throws -> (data: Data, size: Size2) {
12-
let png = try PNGDecoder().decode(data)
13-
return (png.data, Size2(Float(png.width), Float(png.height)))
40+
public func loadTexture(options: TextureImporterOptions) throws -> (data: Data, size: Size2) {
41+
return (self.data, self.size)
1442
}
1543

16-
public static func canProcessFile(_ file: URL) -> Bool {
17-
guard PNGDecoder.isSupported else { return false }
18-
return file.pathExtension.caseInsensitiveCompare("png") == .orderedSame
44+
public static func supportedFileExtensions() -> [String] {
45+
return ["png"]
1946
}
2047
}
2148

Sources/GateEngine/Resources/Import & Export/Importers/RawGeometryImporter.swift

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,36 @@ import Foundation
1313
The file extension of the asset to load must match `RawGeometryImporter.fileExtension`
1414
*/
1515
public final class RawGeometryImporter: GeometryImporter {
16+
var data: Data! = nil
1617
public required init() {}
1718

18-
public func loadData(path: String, options: GeometryImporterOptions) async throws -> RawGeometry {
19-
let data = try await Game.shared.platform.loadResource(from: path)
20-
return try RawGeometryDecoder().decode(data)
19+
public func synchronousPrepareToImportResourceFrom(path: String) throws(GateEngineError) {
20+
do {
21+
self.data = try Platform.current.synchronousLoadResource(from: path)
22+
}catch{
23+
throw GateEngineError(error)
24+
}
2125
}
22-
23-
public static func canProcessFile(_ file: URL) -> Bool {
24-
return file.pathExtension.caseInsensitiveCompare(Self.fileExtension) == .orderedSame
26+
public func prepareToImportResourceFrom(path: String) async throws(GateEngineError) {
27+
do {
28+
self.data = try await Game.shared.platform.loadResource(from: path)
29+
}catch{
30+
throw GateEngineError(error)
31+
}
32+
}
33+
34+
public func loadGeometry(options: GeometryImporterOptions) async throws(GateEngineError) -> RawGeometry {
35+
do {
36+
return try RawGeometryDecoder().decode(data)
37+
}catch{
38+
throw GateEngineError(error)
39+
}
2540
}
2641

2742
/// The expected file extension
2843
/// Write data created with `RawGeometryEncoder` to a file with this extension to be imported by this `GeometryImporter`
2944
public static let fileExtension: String = "gaterg"
45+
public static func supportedFileExtensions() -> [String] {
46+
return [Self.fileExtension]
47+
}
3048
}

0 commit comments

Comments
 (0)