Skip to content

Commit 64b44f5

Browse files
committed
feat: Enhance concurrency and thread safety across multiple components
1 parent 7daf4e9 commit 64b44f5

20 files changed

+119
-231
lines changed
Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,7 @@
1-
//
2-
// ExtensionNotificationCenter.swift
3-
//
4-
//
5-
// Created by Tatsuya Tanaka on 2023/02/25.
6-
//
7-
81
import Foundation
2+
import os
93

10-
public enum ExtensionNotification: String, Equatable {
4+
public enum ExtensionNotification: String, Equatable, Sendable {
115
case startCameraExtensionStream
126
case stopAllCameraExtensionStreams
137

@@ -23,11 +17,11 @@ public enum ExtensionNotification: String, Equatable {
2317
}
2418
}
2519

26-
public final class ExtensionNotificationCenter {
20+
public final class ExtensionNotificationCenter: Sendable {
2721
public static let `default` = ExtensionNotificationCenter()
2822

29-
private let center = CFNotificationCenterGetDarwinNotifyCenter()
30-
private var observers: [ExtensionNotification: () -> Void] = [:]
23+
private nonisolated(unsafe) let center = CFNotificationCenterGetDarwinNotifyCenter()
24+
private let observers = OSAllocatedUnfairLock(initialState: [ExtensionNotification: @Sendable () -> Void]())
3125

3226
public func post(_ notification: ExtensionNotification) {
3327
CFNotificationCenterPostNotification(
@@ -39,14 +33,14 @@ public final class ExtensionNotificationCenter {
3933
)
4034
}
4135

42-
public func setObserver(for notification: ExtensionNotification, using: @escaping () -> Void) {
43-
defer {
36+
public func setObserver(for notification: ExtensionNotification, using: @escaping @Sendable () -> Void) {
37+
let alreadyRegistered = observers.withLock { observers in
38+
let exists = observers.keys.contains(notification)
4439
observers[notification] = using
40+
return exists
4541
}
4642

47-
guard !observers.keys.contains(notification) else {
48-
return
49-
}
43+
guard !alreadyRegistered else { return }
5044

5145
let observer = UnsafeRawPointer(Unmanaged.passUnretained(self).toOpaque())
5246
CFNotificationCenterAddObserver(
@@ -55,7 +49,7 @@ public final class ExtensionNotificationCenter {
5549
{ _, observer, name, _, _ in
5650
if let observer = observer, let name = name, let notification = ExtensionNotification(name: name) {
5751
let observerSelf = Unmanaged<ExtensionNotificationCenter>.fromOpaque(observer).takeUnretainedValue()
58-
observerSelf.observers[notification]?()
52+
observerSelf.observers.withLock { $0[notification]?() }
5953
}
6054
},
6155
notification.rawValue as CFString,
@@ -65,7 +59,7 @@ public final class ExtensionNotificationCenter {
6559
}
6660

6761
public func removeAllObservers() {
68-
observers.removeAll()
62+
observers.withLock { $0.removeAll() }
6963
CFNotificationCenterRemoveEveryObserver(center, Unmanaged.passRetained(self).toOpaque())
7064
}
7165
}
Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,23 @@
1-
//
2-
// Debug.swift
3-
//
4-
//
5-
// Created by Tatsuya Tanaka on 2022/05/09.
6-
//
7-
//
81
import AppKit
92
import SwiftUI
103

11-
private var debugLog: ((String) -> Void)?
4+
@MainActor private var debugLog: ((String) -> Void)?
125

136

147
@_cdecl("uniRegisterDebugLog")
15-
public func uniRegisterDebugLog(_ function: @escaping @convention(c) (UnsafePointer<CChar>) -> Void) {
8+
@MainActor public func uniRegisterDebugLog(_ function: @escaping @convention(c) (UnsafePointer<CChar>) -> Void) {
169
debugLog = { function(($0 as NSString).utf8String!) }
1710
}
1811

1912
public func uniDebugLog(_ message: String) {
2013
guard Thread.isMainThread else {
21-
DispatchQueue.main.async {
14+
Task { @MainActor in
2215
uniDebugLog(message)
2316
}
2417
return
2518
}
2619

27-
debugLog?(message)
20+
MainActor.assumeIsolated {
21+
debugLog?(message)
22+
}
2823
}
Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
1-
//
2-
// DeeplinkHandler.swift
3-
//
4-
//
5-
// Created by tattn on 2025/11/15.
6-
//
7-
81
import Foundation
92

103
@_cdecl("uniHandleDeepLink")
11-
public func uniHandleDeepLink(_ url: UnsafePointer<CChar>) {
4+
@MainActor public func uniHandleDeepLink(_ url: UnsafePointer<CChar>) {
125
let urlString = String(cString: url)
136
DeeplinkHandler.handleURL(URL(string: urlString)!)
147
}
158

9+
@MainActor
1610
public enum DeeplinkHandler {
1711
public static var handleURL: (URL) -> Void = { _ in }
1812
}
Lines changed: 3 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,5 @@
1-
//
2-
// RenderTexture.swift
3-
//
4-
//
5-
// Created by Tatsuya Tanaka on 2022/03/25.
6-
//
7-
8-
import MetalKit
1+
@preconcurrency import MetalKit
2+
import os
93

104
public enum RenderTextureType: Int32 {
115
case photo
@@ -14,7 +8,7 @@ public enum RenderTextureType: Int32 {
148
case web
159
}
1610

17-
public final class MainTexture {
11+
public final class MainTexture: @unchecked Sendable {
1812
public static let shared = MainTexture()
1913

2014
public private(set) var texture = CIImage(color: .black).cropped(to: .init(x: 0, y: 0, width: 1280, height: 720))
@@ -34,55 +28,3 @@ public final class MainTexture {
3428
self.texture = CIImage(mtlTexture: texture, options: nil)!
3529
}
3630
}
37-
38-
public func __bridge<T : AnyObject>(_ ptr: UnsafeRawPointer) -> T {
39-
Unmanaged.fromOpaque(ptr).takeUnretainedValue()
40-
}
41-
42-
@_cdecl("uniRegisterMainTexture")
43-
public func uniRegisterMainTexture(imagePointer: UnsafeRawPointer?) {
44-
guard let imagePointer else { return }
45-
let bridgedMtlTexture: any MTLTexture = __bridge(imagePointer)
46-
47-
#if FOR_DEBUG
48-
os_log(.debug, " \(String(describing: bridgedMtlTexture).dropFirst(80), privacy: .public)")
49-
#endif
50-
51-
// let mtlTexture = bridgedMtlTexture.makeTextureView(pixelFormat: .rgba8Unorm_srgb)!
52-
MainTexture.shared.setTexture(bridgedMtlTexture)
53-
}
54-
55-
56-
57-
/* Information about the main texture
58-
59-
<AGXG13XFamilyTexture: 0x12e87f730>
60-
label =
61-
textureType = MTLTextureType2D
62-
pixelFormat = MTLPixelFormatRGBA8Unorm_sRGB
63-
width = 1920
64-
height = 1080
65-
depth = 1
66-
arrayLength = 1
67-
mipmapLevelCount = 1
68-
sampleCount = 1
69-
cpuCacheMode = MTLCPUCacheModeDefaultCache
70-
storageMode = MTLStorageModeShared
71-
hazardTrackingMode = MTLHazardTrackingModeTracked
72-
resourceOptions = MTLResourceCPUCacheModeDefaultCache MTLResourceStorageModeShared MTLResourceHazardTrackingModeTracked
73-
usage = MTLTextureUsageShaderRead MTLTextureUsageRenderTarget
74-
shareable = 0
75-
framebufferOnly = 0
76-
purgeableState = MTLPurgeableStateNonVolatile
77-
swizzle = [MTLTextureSwizzleRed, MTLTextureSwizzleGreen, MTLTextureSwizzleBlue, MTLTextureSwizzleAlpha]
78-
isCompressed = 1
79-
parentTexture = <null>
80-
parentRelativeLevel = 0
81-
parentRelativeSlice = 0
82-
buffer = <null>
83-
bufferOffset = 0
84-
bufferBytesPerRow = 0
85-
iosurface = 0x0
86-
iosurfacePlane = 0
87-
allowGPUOptimizedContents = YES
88-
*/

app/xcode/Sources/VCamBridge/UniBridge+Typed.swift

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
1-
//
2-
// UniBridge+Typed.swift
3-
//
4-
//
5-
// Created by tattn on 2025/12/07.
6-
//
7-
81
import Foundation
92

103
// MARK: - Method ID Enum
11-
public struct UniBridgeMethodId: RawRepresentable {
4+
public struct UniBridgeMethodId: RawRepresentable, Sendable {
125
static let playMotion = Self.init(rawValue: 0)
136
static let stopMotion = Self.init(rawValue: 1)
147
static let applyExpression = Self.init(rawValue: 2)
@@ -62,7 +55,7 @@ public struct ScreenResolutionPayload: Equatable {
6255

6356
// MARK: - Bridge Callback
6457
public extension UniBridge {
65-
fileprivate(set) static var methodCallback: (UniBridgeMethodId, UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void = { _, _, _ in }
58+
nonisolated(unsafe) fileprivate(set) static var methodCallback: (UniBridgeMethodId, UnsafeMutableRawPointer?, UnsafeMutableRawPointer?) -> Void = { _, _, _ in }
6659
}
6760

6861
// MARK: - Bridge Implementation

app/xcode/Sources/VCamBridge/UniBridge.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
/// GENERATED BY ./scripts/generate_bridge
22
import SwiftUI
3-
public final class UniBridge {
3+
public final class UniBridge: @unchecked Sendable {
44
public static let shared = UniBridge()
55
private init() {}
6-
public enum IntType: Int32 {
6+
public enum IntType: Int32, Sendable {
77
case lensFlare = 0
88
case facialExpression = 1
99
case objectSelected = 2
@@ -15,7 +15,7 @@ public final class UniBridge {
1515
public var objectSelected: Binding<Int32> { intMapper.binding(.objectSelected) }
1616
public private(set) lazy var qualityLevel = intMapper.set(.qualityLevel)
1717

18-
public enum FloatType: Int32 {
18+
public enum FloatType: Int32, Sendable {
1919
case camera = 0
2020
case light = 1
2121
case postExposure = 2
@@ -65,7 +65,7 @@ public final class UniBridge {
6565
public private(set) lazy var swivelOffset = floatMapper.set(.swivelOffset)
6666
public private(set) lazy var trackingSmoothing = floatMapper.set(.trackingSmoothing)
6767

68-
public enum BoolType: Int32 {
68+
public enum BoolType: Int32, Sendable {
6969
case useAutoMode = 0
7070
case useShadow = 1
7171
case usePostEffect = 2
@@ -91,7 +91,7 @@ public final class UniBridge {
9191
public private(set) lazy var lipSyncWebCam = boolMapper.set(.lipSyncWebCam)
9292
public var hasPerfectSyncBlendShape: Bool { boolMapper.get(.hasPerfectSyncBlendShape) }
9393

94-
public enum StringType: Int32 {
94+
public enum StringType: Int32, Sendable {
9595
case message = 0
9696
case loadVRM = 1
9797
case loadModel = 2
@@ -103,7 +103,7 @@ public final class UniBridge {
103103
public private(set) lazy var loadModel = stringMapper.set(.loadModel)
104104
public private(set) lazy var showEmojiStamp = stringMapper.set(.showEmojiStamp)
105105

106-
public enum TriggerType: Int32 {
106+
public enum TriggerType: Int32, Sendable {
107107
case openVRoidHub = 0
108108
case resetCamera = 1
109109
case deleteObject = 2
@@ -123,7 +123,7 @@ public final class UniBridge {
123123
public private(set) lazy var resumeApp = triggerMapper.trigger(.resumeApp)
124124
public private(set) lazy var quitApp = triggerMapper.trigger(.quitApp)
125125

126-
public enum StructType: Int32 {
126+
public enum StructType: Int32, Sendable {
127127
case backgroundColor = 0
128128
case environmentLightColor = 1
129129
case colorFilter = 2
@@ -137,7 +137,7 @@ public final class UniBridge {
137137
public private(set) lazy var bloomColor = structMapper.set(.bloomColor, type: Color.self)
138138
public private(set) lazy var vignetteColor = structMapper.set(.vignetteColor, type: Color.self)
139139

140-
public enum ArrayType: Int32 {
140+
public enum ArrayType: Int32, Sendable {
141141
case hands = 0
142142
case fingers = 1
143143
case receiveVCamBlendShape = 2

app/xcode/Sources/VCamBridge/ValueBinding.swift

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
//
2-
// ValueBinding.swift
3-
//
4-
//
5-
// Created by Tatsuya Tanaka on 2022/03/11.
6-
//
7-
81
import struct CoreGraphics.CGFloat
92
import class AppKit.NSColor
103
import struct SwiftUI.Binding
114
import struct SwiftUI.Color
125

13-
public final class ValueBinding<Value, Kind: RawRepresentable> where Kind.RawValue == Int32 {
6+
public final class ValueBinding<Value, Kind: RawRepresentable>: @unchecked Sendable where Kind.RawValue == Int32, Kind: Sendable {
147
public var getValue: ((Kind) -> Value)?
158
public var setValue: (Kind, Value) -> Void = { _, _ in }
169

@@ -22,8 +15,8 @@ public final class ValueBinding<Value, Kind: RawRepresentable> where Kind.RawVal
2215
}
2316
}
2417

25-
extension ValueBinding where Value: ValueBindingDefaultValue {
26-
@inlinable public func binding(_ type: Kind, onGet: @escaping (Value) -> Void = { _ in }, onSet: @escaping (Value) -> Void = { _ in }) -> Binding<Value> {
18+
extension ValueBinding where Value: ValueBindingDefaultValue & Sendable {
19+
@inlinable public func binding(_ type: Kind, onGet: @escaping @Sendable (Value) -> Void = { _ in }, onSet: @escaping @Sendable (Value) -> Void = { _ in }) -> Binding<Value> {
2720
.init { [weak self] in
2821
let value = self?.getValue?(type) ?? Value.defaultValue
2922
onGet(value)
@@ -50,7 +43,7 @@ extension ValueBinding where Value == Void {
5043
}
5144

5245
extension ValueBinding where Value == UnsafeMutableRawPointer {
53-
@inlinable public func binding<T: ValueBindingStructType>(_ kind: Kind, type: T.Type = T.self, onGet: @escaping (T) -> Void = { _ in }, onSet: @escaping (T) -> Void = { _ in }) -> Binding<T> {
46+
@inlinable public func binding<T: ValueBindingStructType & Sendable>(_ kind: Kind, type: T.Type = T.self, onGet: @escaping @Sendable (T) -> Void = { _ in }, onSet: @escaping @Sendable (T) -> Void = { _ in }) -> Binding<T> {
5447
.init { [weak self] in
5548
guard let value = self?.getValue?(kind), UInt(bitPattern: value) != 0 else {
5649
return T.defaultValue
@@ -79,7 +72,7 @@ extension ValueBinding where Value == UnsafeMutableRawPointer {
7972
}
8073
}
8174

82-
@inlinable public func binding<T: BridgeArrayType>(_ kind: Kind, size: Int, type: T.Type = T.self, onGet: @escaping (T) -> Void = { _ in }, onSet: @escaping (T) -> Void = { _ in }) -> Binding<T> {
75+
@inlinable public func binding<T: BridgeArrayType & Sendable>(_ kind: Kind, size: Int, type: T.Type = T.self, onGet: @escaping @Sendable (T) -> Void = { _ in }, onSet: @escaping @Sendable (T) -> Void = { _ in }) -> Binding<T> {
8376
.init { [weak self] in
8477
guard let retrievedValue: T = self?.get(kind, size: size) else {
8578
return T.defaultValue

0 commit comments

Comments
 (0)