Skip to content

Commit d8ab77e

Browse files
committed
feat: Enhance concurrency support and code structure
- Added `Sendable` conformance to various structs and enums to improve thread safety. - Introduced `@MainActor` annotations to ensure UI updates occur on the main thread. - Refactored `Version` struct to simplify bundle access. - Updated `Binding` extensions to support `Sendable` types. - Improved error handling and state management in `DeviceAuthorization` and `PasteboardObserver`. - Enhanced `VCamAction` and related types for better concurrency handling. - Added new `FacialEstimator` and `FacialExpressionEstimator` structs for improved facial tracking. - Refined `VCamUI` components for better performance and maintainability. - Updated various UI components to ensure they are thread-safe and responsive.
1 parent 6960a98 commit d8ab77e

File tree

60 files changed

+204
-343
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+204
-343
lines changed

app/xcode/App/Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// swift-tools-version:5.9
1+
// swift-tools-version: 6.2
22

33
import PackageDescription
44

app/xcode/Sources/VCamBridge/Cursor.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
//
2-
// Cursor.swift
3-
//
4-
//
5-
// Created by Tatsuya Tanaka on 2022/05/21.
6-
//
7-
81
import AppKit
92
import VCamUIFoundation
103

@@ -26,7 +19,7 @@ enum CursorType: Int {
2619
}
2720

2821
@_cdecl("uniPushCursor")
29-
public func uniPushCursor(_ type: Int) {
22+
@MainActor public func uniPushCursor(_ type: Int) {
3023
guard let cursor = CursorType(rawValue: type) else { return }
3124
switch cursor {
3225
case .northWestSouthEastResize, .northEastSouthWestResize, .move:
@@ -36,6 +29,6 @@ public func uniPushCursor(_ type: Int) {
3629
}
3730

3831
@_cdecl("uniPopCursor")
39-
public func uniPopCursor() {
32+
@MainActor public func uniPopCursor() {
4033
NSCursor.popForSwiftUI()
4134
}
Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,12 @@
1-
//
2-
// FilePicker.swift
3-
//
4-
//
5-
// Created by Tatsuya Tanaka on 2022/05/05.
6-
//
7-
81
import AppKit
92
import VCamUIFoundation
103

114
@_cdecl("uniOpenFile")
12-
public func uniOpenFile(_ fileType: Int, _ handler: @escaping @convention(c) (UnsafePointer<CChar>) -> Void) {
5+
@MainActor public func uniOpenFile(_ fileType: Int, _ handler: @escaping @Sendable @convention(c) (UnsafePointer<CChar>) -> Void) {
136
guard let type = FileUtility.FileType(rawValue: fileType) else {
147
handler(("" as NSString).utf8String!)
158
return
169
}
17-
if let url = FileUtility.openFile(type: type) {
18-
handler((url.path as NSString).utf8String!)
19-
} else {
20-
handler(("" as NSString).utf8String!)
21-
}
10+
let path = FileUtility.openFile(type: type)?.path ?? ""
11+
handler((path as NSString).utf8String!)
2212
}

app/xcode/Sources/VCamBridge/TrackingMappingEntry.swift

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -96,13 +96,13 @@ public extension TrackingMappingEntry.Key {
9696
}
9797
}
9898

99-
extension TrackingMappingEntry.InputKey: Codable {
100-
private enum CodingKeys: String, CodingKey {
101-
case key, boundsMin, boundsMax, rangeMin, rangeMax
102-
}
99+
private enum RangeKeyCodingKeys: String, CodingKey {
100+
case key, boundsMin, boundsMax, rangeMin, rangeMax
101+
}
103102

103+
extension TrackingMappingEntry.InputKey: Codable {
104104
public init(from decoder: any Decoder) throws {
105-
let container = try decoder.container(keyedBy: CodingKeys.self)
105+
let container = try decoder.container(keyedBy: RangeKeyCodingKeys.self)
106106
let key = try container.decode(String.self, forKey: .key)
107107
let boundsMin = try container.decode(Float.self, forKey: .boundsMin)
108108
let boundsMax = try container.decode(Float.self, forKey: .boundsMax)
@@ -112,7 +112,7 @@ extension TrackingMappingEntry.InputKey: Codable {
112112
}
113113

114114
public func encode(to encoder: any Encoder) throws {
115-
var container = encoder.container(keyedBy: CodingKeys.self)
115+
var container = encoder.container(keyedBy: RangeKeyCodingKeys.self)
116116
try container.encode(key, forKey: .key)
117117
try container.encode(bounds.lowerBound, forKey: .boundsMin)
118118
try container.encode(bounds.upperBound, forKey: .boundsMax)
@@ -122,12 +122,8 @@ extension TrackingMappingEntry.InputKey: Codable {
122122
}
123123

124124
extension TrackingMappingEntry.OutputKey: Codable {
125-
private enum CodingKeys: String, CodingKey {
126-
case key, boundsMin, boundsMax, rangeMin, rangeMax
127-
}
128-
129125
public init(from decoder: any Decoder) throws {
130-
let container = try decoder.container(keyedBy: CodingKeys.self)
126+
let container = try decoder.container(keyedBy: RangeKeyCodingKeys.self)
131127
let key = try container.decode(String.self, forKey: .key)
132128
let boundsMin = try container.decode(Float.self, forKey: .boundsMin)
133129
let boundsMax = try container.decode(Float.self, forKey: .boundsMax)
@@ -137,7 +133,7 @@ extension TrackingMappingEntry.OutputKey: Codable {
137133
}
138134

139135
public func encode(to encoder: any Encoder) throws {
140-
var container = encoder.container(keyedBy: CodingKeys.self)
136+
var container = encoder.container(keyedBy: RangeKeyCodingKeys.self)
141137
try container.encode(key, forKey: .key)
142138
try container.encode(bounds.lowerBound, forKey: .boundsMin)
143139
try container.encode(bounds.upperBound, forKey: .boundsMax)

app/xcode/Sources/VCamCamera/CameraExtension.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,3 +89,5 @@ extension CameraExtension: OSSystemExtensionRequestDelegate {
8989
.replace
9090
}
9191
}
92+
93+
extension OSSystemExtensionProperties: @retroactive @unchecked Sendable {}

app/xcode/Sources/VCamCamera/NSImage+.swift

Lines changed: 0 additions & 17 deletions
This file was deleted.

app/xcode/Sources/VCamData/DisplayParameter.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ public struct DisplayParameter: Codable, Identifiable, Equatable {
3030
public init() {}
3131
}
3232

33-
public struct Color: Codable, Equatable {
33+
public struct Color: Codable, Equatable, Sendable {
3434
public var r: Float
3535
public var g: Float
3636
public var b: Float
@@ -77,6 +77,7 @@ private struct DisplayParameterPresetsFile: Codable {
7777
var parameters: [DisplayParameter]
7878
}
7979

80+
@MainActor
8081
@Observable
8182
public final class DisplayParameterPresets {
8283
public static let shared = DisplayParameterPresets()
@@ -157,13 +158,7 @@ public final class DisplayParameterPresets {
157158

158159
// MARK: - DisplayParameterPreset (for UI compatibility)
159160

160-
public struct DisplayParameterPreset: CaseIterable, Hashable, Identifiable, CustomStringConvertible {
161-
public static var allCases: [DisplayParameterPreset] {
162-
DisplayParameterPresets.shared.parameters.map {
163-
DisplayParameterPreset(id: $0.id, description: $0.name)
164-
}
165-
}
166-
161+
public struct DisplayParameterPreset: Hashable, Identifiable, CustomStringConvertible, Sendable {
167162
public static let newPreset = Self.init(id: "", description: L10n.newPreset.text)
168163

169164
public let id: String

app/xcode/Sources/VCamData/DisplayParameterController.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import Foundation
1111
import VCamBridge
1212

13+
@MainActor
1314
public final class DisplayParameterController {
1415
private unowned let state: UniState
1516
private var storedParameterId = UserDefaults.standard.value(for: .displayParameterId)

app/xcode/Sources/VCamData/ModelManager.swift

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import AppKit
22
import Observation
33
import VCamEntity
44

5+
@MainActor
56
@Observable
67
public final class ModelManager {
78
public static let shared = ModelManager()
@@ -17,7 +18,7 @@ public final class ModelManager {
1718

1819
#if DEBUG
1920
public init(models: [Models.Model], lastLoadedModelId: UUID? = nil) {
20-
self.modelItems = models.map { ModelItem(model: $0, status: .valid, thumbnail: $0.loadThumbnail()) }
21+
self.modelItems = models.map { ModelItem(model: $0, status: .valid, thumbnail: $0.loadThumbnail()?.png) }
2122
self.lastLoadedModelId = lastLoadedModelId
2223
}
2324
#endif
@@ -62,7 +63,7 @@ public final class ModelManager {
6263
}.value
6364

6465
if let image = metadata?.image {
65-
saveThumbnail(image, for: model)
66+
try? saveThumbnail(image, for: model)
6667
}
6768
}
6869

@@ -100,14 +101,14 @@ public final class ModelManager {
100101
}
101102

102103
public func setThumbnail(for item: ModelItem, from imageURL: URL) throws {
103-
guard let image = NSImage(contentsOf: imageURL) else {
104+
guard let image = NSImage(contentsOf: imageURL), let imageData = image.png else {
104105
throw ModelManagerError.invalidImage
105106
}
106-
saveThumbnail(image, for: item.model)
107-
updateThumbnail(for: item, image: image)
107+
try saveThumbnail(imageData, for: item.model)
108+
updateThumbnail(for: item, image: imageData)
108109
}
109110

110-
private func updateThumbnail(for item: ModelItem, image: NSImage) {
111+
private func updateThumbnail(for item: ModelItem, image: Data) {
111112
guard let index = modelItems.firstIndex(where: { $0.id == item.id }) else { return }
112113
modelItems[index] = ModelItem(model: item.model, status: item.status, thumbnail: image)
113114
}
@@ -130,7 +131,7 @@ public final class ModelManager {
130131
modelItems = modelItems.map { item in
131132
let url = item.model.modelURL
132133
let status: ModelItem.ModelStatus = FileManager.default.fileExists(atPath: url.path) ? .valid : .missing
133-
return ModelItem(model: item.model, status: status, thumbnail: item.thumbnail ?? item.model.loadThumbnail())
134+
return ModelItem(model: item.model, status: status, thumbnail: item.thumbnail ?? item.model.loadThumbnail()?.png)
134135
}
135136
scanForNewModels()
136137
saveMeta()
@@ -158,7 +159,7 @@ public final class ModelManager {
158159
let attributes = try? FileManager.default.attributesOfItem(atPath: modelFile.path)
159160
let createdAt = attributes?[.creationDate] as? Date ?? .now
160161
let modelInfo = Models.Model(name: name, type: Models.modelType, createdAt: createdAt)
161-
modelItems.append(ModelItem(model: modelInfo, status: .valid, thumbnail: modelInfo.loadThumbnail()))
162+
modelItems.append(ModelItem(model: modelInfo, status: .valid, thumbnail: modelInfo.loadThumbnail()?.png))
162163
}
163164
} catch {
164165
print("Failed to scan models: \(error)")
@@ -167,7 +168,7 @@ public final class ModelManager {
167168

168169
@discardableResult
169170
private func addModel(_ model: Models.Model) -> ModelItem {
170-
let item = ModelItem(model: model, status: .valid, thumbnail: model.loadThumbnail())
171+
let item = ModelItem(model: model, status: .valid, thumbnail: model.loadThumbnail()?.png)
171172
guard !modelItems.contains(where: { $0.id == model.id }) else { return item }
172173
modelItems.insert(item, at: 0)
173174
saveMeta()
@@ -188,7 +189,7 @@ public final class ModelManager {
188189
let meta = try? JSONDecoder().decode(Models.self, from: data) else {
189190
return
190191
}
191-
modelItems = meta.models.map { ModelItem(model: $0, status: .valid, thumbnail: $0.loadThumbnail()) }
192+
modelItems = meta.models.map { ModelItem(model: $0, status: .valid, thumbnail: $0.loadThumbnail()?.png) }
192193
lastLoadedModelId = meta.lastModelId
193194
}
194195

@@ -204,18 +205,8 @@ public final class ModelManager {
204205
}
205206
}
206207

207-
private func saveThumbnail(_ image: NSImage, for model: Models.Model) {
208-
guard let tiffData = image.tiffRepresentation,
209-
let bitmap = NSBitmapImageRep(data: tiffData),
210-
let pngData = bitmap.representation(using: .png, properties: [:]) else {
211-
return
212-
}
213-
214-
do {
215-
try pngData.write(to: model.rootURL.appending(path: Models.Model.thumbnailFileName))
216-
} catch {
217-
print("Failed to save thumbnail: \(error)")
218-
}
208+
private func saveThumbnail(_ image: Data, for model: Models.Model) throws {
209+
try image.write(to: model.rootURL.appending(path: Models.Model.thumbnailFileName))
219210
}
220211
}
221212

app/xcode/Sources/VCamData/ModelMetaLoader.swift

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ import AppKit
33
import VRMKit
44
#endif
55

6-
public struct ModelMeta: Hashable {
6+
public struct ModelMeta: Hashable, Sendable {
77
public var name: String
8-
public var image: NSImage?
8+
public var image: Data?
99
}
1010

1111
enum ModelMetaLoader {
@@ -16,13 +16,13 @@ enum ModelMetaLoader {
1616
let vrm1 = try loader.load(VRM1.self, withURL: url)
1717
return ModelMeta(
1818
name: vrm1.meta.name,
19-
image: try? loader.loadThumbnail(from: vrm1)
19+
image: try? loader.loadThumbnail(from: vrm1).png
2020
)
2121
} catch {
2222
let vrm0 = try loader.load(VRM.self, withURL: url)
2323
return ModelMeta(
2424
name: vrm0.meta.title ?? url.deletingPathExtension().lastPathComponent,
25-
image: try? loader.loadThumbnail(from: vrm0)
25+
image: try? loader.loadThumbnail(from: vrm0).png
2626
)
2727
}
2828
}

0 commit comments

Comments
 (0)