@@ -9,6 +9,7 @@ import Network
99import Combine
1010import VCamBridge
1111import Accelerate
12+ import VCamLogger
1213
1314@Observable
1415public final class FacialMocapReceiver {
@@ -22,6 +23,12 @@ public final class FacialMocapReceiver {
2223
2324 @MainActor public private( set) var connectionStatus = ConnectionStatus . disconnected
2425
26+ @ObservationIgnored private var shouldAutoReconnect = true
27+ @ObservationIgnored private var lastConnectedIP : String ?
28+ @ObservationIgnored private var timeoutTask : Task < Void , Never > ?
29+
30+ private static let dataTimeoutSeconds : UInt64 = 2
31+
2532 private final class SmoothingBox {
2633 var smoothing : TrackingSmoothing
2734
@@ -30,12 +37,6 @@ public final class FacialMocapReceiver {
3037 }
3138 }
3239
33- public enum ConnectionStatus {
34- case disconnected
35- case connecting
36- case connected
37- }
38-
3940 enum ReceiverResult {
4041 case success
4142 case cancel
@@ -60,7 +61,9 @@ public final class FacialMocapReceiver {
6061
6162 @MainActor
6263 public func connect( ip: String ) async throws {
63- await stop ( )
64+ await stopInternal ( )
65+ shouldAutoReconnect = true
66+ lastConnectedIP = ip
6467
6568#if FEATURE_3
6669 let port = NWEndpoint . Port ( integerLiteral: 49983 )
@@ -72,45 +75,90 @@ public final class FacialMocapReceiver {
7275 switch result {
7376 case . success: ( )
7477 case . cancel, . error:
75- self ? . stopAsync ( )
78+ Task { @MainActor in
79+ await self ? . handleDisconnection ( )
80+ }
7681 }
7782 }
7883
7984 requestConnection ( ip: ip, port: port) { [ weak self] result in
8085 switch result {
8186 case . success: ( )
8287 case . cancel, . error:
83- self ? . stopAsync ( )
88+ Task { @MainActor in
89+ await self ? . handleDisconnection ( )
90+ }
8491 }
8592 }
8693 }
8794
8895 @MainActor
8996 public func stop( ) async {
97+ shouldAutoReconnect = false
98+ await stopInternal ( )
99+ }
100+
101+ @MainActor
102+ private func stopInternal( ) async {
103+ timeoutTask? . cancel ( )
104+ timeoutTask = nil
105+
90106 if let listener = listener {
91107 listener. stateUpdateHandler = nil
92108 listener. newConnectionHandler = nil
93109 listener. cancel ( )
94110 self . listener = nil
95111 }
96112
113+ connection? . stateUpdateHandler = nil
97114 connection? . cancel ( )
98115 connection = nil
99116 connectionStatus = . disconnected
100117
101118 stopResamplers ( )
102119 }
103120
121+ @MainActor
122+ private func resetTimeoutTimer( ) {
123+ timeoutTask? . cancel ( )
124+ timeoutTask = Task { @MainActor [ weak self] in
125+ do {
126+ try await Task . sleep ( nanoseconds: Self . dataTimeoutSeconds * NSEC_PER_SEC)
127+ Logger . log ( " Data timeout - resetting listener " )
128+ await self ? . handleTimeout ( )
129+ } catch { }
130+ }
131+ }
132+
133+ @MainActor
134+ private func handleTimeout( ) async {
135+ guard connectionStatus == . connected, shouldAutoReconnect, let ip = lastConnectedIP else { return }
136+ await stopInternal ( )
137+ do {
138+ try await connect ( ip: ip)
139+ } catch {
140+ Logger . log ( " Restart failed: \( error. localizedDescription) " )
141+ }
142+ }
143+
104144 func updateSmoothing( _ smoothing: TrackingSmoothing ) {
105145 smoothingBox. smoothing = smoothing
106146 if !smoothing. isEnabled {
107147 stopResamplers ( )
108148 }
109149 }
110150
111- private func stopAsync( ) {
112- Task {
113- await self . stop ( )
151+ @MainActor
152+ private func handleDisconnection( ) async {
153+ guard shouldAutoReconnect, let ip = lastConnectedIP else {
154+ await stopInternal ( )
155+ return
156+ }
157+ await stopInternal ( )
158+ do {
159+ try await connect ( ip: ip)
160+ } catch {
161+ Logger . log ( " Restart failed: \( error. localizedDescription) " )
114162 }
115163 }
116164
@@ -148,10 +196,13 @@ public final class FacialMocapReceiver {
148196}
149197
150198private extension NWConnection {
151- func receiveData( with oniFacialMocapReceived: @escaping ( FacialMocapData ) -> Void ) {
199+ func receiveData(
200+ with oniFacialMocapReceived: @escaping ( FacialMocapData ) -> Void ,
201+ onDataReceived: @escaping ( ) -> Void
202+ ) {
152203 receive ( minimumIncompleteLength: 1 , maximumLength: 8192 ) { [ weak self] content, contentContext, isComplete, error in
153204 defer {
154- self ? . receiveData ( with: oniFacialMocapReceived)
205+ self ? . receiveData ( with: oniFacialMocapReceived, onDataReceived : onDataReceived )
155206 }
156207
157208 guard error == nil ,
@@ -160,7 +211,7 @@ private extension NWConnection {
160211 let mocapData = FacialMocapData ( rawData: rawData) else {
161212 return
162213 }
163-
214+ onDataReceived ( )
164215 oniFacialMocapReceived ( mocapData)
165216 }
166217 }
@@ -194,17 +245,26 @@ extension FacialMocapReceiver {
194245 switch state {
195246 case . setup, . preparing: ( )
196247 case . waiting( let error) :
248+ Logger . log ( " Connection waiting: \( error. localizedDescription) " )
197249 if case . posix( let posixError) = error, posixError == . ECONNREFUSED {
198250 try ? await Task . sleep ( nanoseconds: NSEC_PER_SEC * 2 )
199251 try ? await self . startServer ( port: port, completion: completion)
200252 }
201253 case . ready:
254+ Logger . log ( " Connection ready " )
202255 self . connectionStatus = . connected
203- connection. receiveData ( with: self . oniFacialMocapReceived)
256+ self . resetTimeoutTimer ( )
257+ connection. receiveData ( with: self . oniFacialMocapReceived, onDataReceived: { [ weak self] in
258+ Task { @MainActor in
259+ self ? . resetTimeoutTimer ( )
260+ }
261+ } )
204262 case . cancelled:
205- self . stopAsync ( )
206- case . failed:
207- try ? await self . startServer ( port: port, completion: completion)
263+ Logger . log ( " Connection cancelled " )
264+ await self . handleDisconnection ( )
265+ case . failed( let error) :
266+ Logger . log ( " Connection failed: \( error. localizedDescription) " )
267+ await self . handleDisconnection ( )
208268 @unknown default : ( )
209269 }
210270 }
0 commit comments