Integrate live phone and watch remote prototype with guarded motion control
This commit is contained in:
@@ -16,6 +16,21 @@ public final class PhoneRelayBridge: NSObject {
|
||||
private let coordinator: any PhoneCommandCoordinating
|
||||
private var peer: WatchPeerGeneration?
|
||||
private var activationReady = false
|
||||
private var lifecycleTask: Task<Void, Never>?
|
||||
private var pending: [UUID: RelayReplyOnce] = [:]
|
||||
private var latestContext: RelayApplicationContext?
|
||||
|
||||
public func publishContext(_ context: RelayApplicationContext) {
|
||||
latestContext = context
|
||||
flushContext()
|
||||
}
|
||||
|
||||
private func flushContext() {
|
||||
guard session.activationState == .activated, let latestContext else { return }
|
||||
if let dictionary = try? latestContext.dictionary() {
|
||||
try? session.updateApplicationContext(dictionary)
|
||||
}
|
||||
}
|
||||
|
||||
public init(session: any RelaySessionSending,
|
||||
dependency: PhoneRelayDependency) {
|
||||
@@ -31,19 +46,24 @@ public final class PhoneRelayBridge: NSObject {
|
||||
}
|
||||
|
||||
public func activationDidComplete(error: Error?) {
|
||||
guard error == nil else {
|
||||
guard error == nil, session.activationState == .activated else {
|
||||
didDeactivate()
|
||||
state = .unavailable
|
||||
activationReady = false
|
||||
return
|
||||
}
|
||||
guard peer == nil else { flushContext(); return }
|
||||
let newPeer = WatchPeerGeneration()
|
||||
peer = newPeer
|
||||
activationReady = false
|
||||
state = .ready(reachable: session.isReachable)
|
||||
Task { [weak self] in
|
||||
let prior = lifecycleTask
|
||||
lifecycleTask = Task { [weak self] in
|
||||
await prior?.value
|
||||
await self?.coordinator.watchBridgeDidActivate(peer: newPeer)
|
||||
guard let self, self.peer == newPeer else { return }
|
||||
self.activationReady = true
|
||||
self.flushContext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +83,12 @@ public final class PhoneRelayBridge: NSObject {
|
||||
peer = nil
|
||||
activationReady = false
|
||||
state = .inactive
|
||||
Task { await coordinator.watchBridgeDidDeactivate(peer: oldPeer) }
|
||||
for reply in pending.values { reply.reply(relayErrorReply(RelayFailure(code: .unavailable))) }
|
||||
let prior = lifecycleTask
|
||||
lifecycleTask = Task {
|
||||
await prior?.value
|
||||
await coordinator.watchBridgeDidDeactivate(peer: oldPeer)
|
||||
}
|
||||
}
|
||||
|
||||
/// Entry point for the native WCSession delegate. Every path completes the
|
||||
@@ -71,18 +96,36 @@ public final class PhoneRelayBridge: NSObject {
|
||||
/// out requests.
|
||||
public func receive(message: [String: Any],
|
||||
replyHandler: @escaping ([String: Any]) -> Void) {
|
||||
let reply = RelayReplyOnce(replyHandler)
|
||||
let inbound = RelayInboundMessage(message: message, replyHandler: replyHandler)
|
||||
guard pending.count < 16 else {
|
||||
inbound.reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
return
|
||||
}
|
||||
let receivedPeer = peer
|
||||
let id = UUID()
|
||||
pending[id] = inbound.reply
|
||||
Task { @MainActor [weak self] in
|
||||
guard let self else {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
inbound.reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
return
|
||||
}
|
||||
await self.route(message: message, reply: reply)
|
||||
guard self.peer == receivedPeer, receivedPeer != nil else {
|
||||
inbound.reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
self.pending.removeValue(forKey: id)
|
||||
return
|
||||
}
|
||||
await self.route(message: inbound.message, reply: inbound.reply)
|
||||
self.pending.removeValue(forKey: id)
|
||||
}
|
||||
Task { @MainActor [weak self] in
|
||||
try? await Task.sleep(for: .seconds(1))
|
||||
guard let self, let reply = self.pending[id] else { return }
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
}
|
||||
}
|
||||
|
||||
private func route(message: [String: Any], reply: RelayReplyOnce) async {
|
||||
guard activationReady, let currentPeer = peer else {
|
||||
guard activationReady, session.activationState == .activated, let currentPeer = peer else {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
return
|
||||
}
|
||||
@@ -101,9 +144,10 @@ public final class PhoneRelayBridge: NSObject {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .malformedRequest)))
|
||||
return
|
||||
}
|
||||
guard let result = await withTimeout({
|
||||
let result = await ({
|
||||
await self.coordinator.beginWatchSession(request, peer: currentPeer)
|
||||
}) else {
|
||||
})()
|
||||
guard peer == currentPeer, activationReady else {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
return
|
||||
}
|
||||
@@ -120,9 +164,10 @@ public final class PhoneRelayBridge: NSObject {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .malformedRequest)))
|
||||
return
|
||||
}
|
||||
guard let result = await withTimeout({
|
||||
let result = await ({
|
||||
await self.coordinator.endWatchSession(request, peer: currentPeer)
|
||||
}) else {
|
||||
})()
|
||||
guard peer == currentPeer, activationReady else {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
return
|
||||
}
|
||||
@@ -139,9 +184,10 @@ public final class PhoneRelayBridge: NSObject {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .malformedRequest)))
|
||||
return
|
||||
}
|
||||
guard let receipt = await withTimeout({
|
||||
let receipt = await ({
|
||||
await self.coordinator.submitWatchCommand(envelope, peer: currentPeer)
|
||||
}) else {
|
||||
})()
|
||||
guard peer == currentPeer, activationReady else {
|
||||
reply.reply(relayErrorReply(RelayFailure(code: .unavailable)))
|
||||
return
|
||||
}
|
||||
@@ -162,18 +208,4 @@ public final class PhoneRelayBridge: NSObject {
|
||||
relayErrorReply(RelayFailure(code: .malformedRequest))
|
||||
}
|
||||
|
||||
private func withTimeout<Value: Sendable>(
|
||||
_ operation: @escaping @Sendable () async -> Value
|
||||
) async -> Value? {
|
||||
await withTaskGroup(of: Value?.self) { group in
|
||||
group.addTask { await operation() }
|
||||
group.addTask {
|
||||
try? await Task.sleep(for: .milliseconds(1_000))
|
||||
return nil
|
||||
}
|
||||
let result = await group.next() ?? nil
|
||||
group.cancelAll()
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user