Фикс: детерминированный роутинг PacketSearch на iOS без подмешивания фоновых результатов
This commit is contained in:
@@ -81,10 +81,9 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
syncBatchLock.unlock()
|
||||
return val
|
||||
}
|
||||
private let searchHandlersLock = NSLock()
|
||||
private let resultHandlersLock = NSLock()
|
||||
private let packetQueueLock = NSLock()
|
||||
private var searchResultHandlers: [UUID: (PacketSearch) -> Void] = [:]
|
||||
private let searchRouter = SearchPacketRouter()
|
||||
private var resultHandlers: [UUID: (PacketResult) -> Void] = [:]
|
||||
|
||||
// Saved credentials for auto-reconnect
|
||||
@@ -145,6 +144,7 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
handshakeComplete = false
|
||||
clearPacketQueue()
|
||||
clearResultHandlers()
|
||||
searchRouter.resetPending()
|
||||
syncBatchLock.lock()
|
||||
_syncBatchActive = false
|
||||
syncBatchLock.unlock()
|
||||
@@ -184,6 +184,7 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
pingTimeoutTask = nil
|
||||
handshakeComplete = false
|
||||
heartbeatTask?.cancel()
|
||||
searchRouter.resetPending()
|
||||
connectionState = .connecting
|
||||
client.forceReconnect()
|
||||
}
|
||||
@@ -208,6 +209,7 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
Self.logger.info("⚡ Fast reconnect — state=\(self.connectionState.rawValue)")
|
||||
handshakeComplete = false
|
||||
heartbeatTask?.cancel()
|
||||
searchRouter.resetPending()
|
||||
connectionState = .connecting
|
||||
client.forceReconnect()
|
||||
}
|
||||
@@ -254,6 +256,7 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
pingTimeoutTask = nil
|
||||
handshakeComplete = false
|
||||
heartbeatTask?.cancel()
|
||||
searchRouter.resetPending()
|
||||
Task { @MainActor in
|
||||
// Guard: only downgrade to .connecting if reconnect hasn't already progressed.
|
||||
// forceReconnect() is called synchronously below — if it completes fast,
|
||||
@@ -268,6 +271,14 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
|
||||
// MARK: - Sending
|
||||
|
||||
func sendSearchPacket(
|
||||
_ packet: PacketSearch,
|
||||
channel: SearchPacketChannel = .unscoped
|
||||
) {
|
||||
searchRouter.enqueueOutgoingRequest(channel: channel)
|
||||
sendPacket(packet)
|
||||
}
|
||||
|
||||
func sendPacket(_ packet: any Packet) {
|
||||
PerformanceLogger.shared.track("protocol.sendPacket")
|
||||
let id = String(type(of: packet).packetId, radix: 16)
|
||||
@@ -285,18 +296,15 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
// MARK: - Search Handlers (Android-like wait/unwait)
|
||||
|
||||
@discardableResult
|
||||
func addSearchResultHandler(_ handler: @escaping (PacketSearch) -> Void) -> UUID {
|
||||
let id = UUID()
|
||||
searchHandlersLock.lock()
|
||||
searchResultHandlers[id] = handler
|
||||
searchHandlersLock.unlock()
|
||||
return id
|
||||
func addSearchResultHandler(
|
||||
channel: SearchPacketChannel = .unscoped,
|
||||
_ handler: @escaping (PacketSearch) -> Void
|
||||
) -> UUID {
|
||||
searchRouter.addHandler(channel: channel, handler)
|
||||
}
|
||||
|
||||
func removeSearchResultHandler(_ id: UUID) {
|
||||
searchHandlersLock.lock()
|
||||
searchResultHandlers.removeValue(forKey: id)
|
||||
searchHandlersLock.unlock()
|
||||
searchRouter.removeHandler(id)
|
||||
}
|
||||
|
||||
// MARK: - Result Handlers (Android parity: waitPacket(0x02))
|
||||
@@ -347,6 +355,7 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
pingVerificationInProgress = false
|
||||
pingTimeoutTask?.cancel()
|
||||
pingTimeoutTask = nil
|
||||
self.searchRouter.resetPending()
|
||||
|
||||
Task { @MainActor in
|
||||
self.connectionState = .disconnected
|
||||
@@ -465,9 +474,11 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
}
|
||||
case 0x03:
|
||||
if let p = packet as? PacketSearch {
|
||||
Self.logger.debug("📥 Search result: \(p.users.count) users, callback=\(self.onSearchResult != nil)")
|
||||
let routedChannel = routeIncomingSearchPacket(p)
|
||||
Self.logger.debug(
|
||||
"📥 Search result: \(p.users.count) users, callback=\(self.onSearchResult != nil), routed=\(String(describing: routedChannel))"
|
||||
)
|
||||
onSearchResult?(p)
|
||||
notifySearchResultHandlers(p)
|
||||
}
|
||||
case 0x05:
|
||||
if let p = packet as? PacketOnlineState {
|
||||
@@ -557,14 +568,8 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
}
|
||||
}
|
||||
|
||||
private func notifySearchResultHandlers(_ packet: PacketSearch) {
|
||||
searchHandlersLock.lock()
|
||||
let handlers = Array(searchResultHandlers.values)
|
||||
searchHandlersLock.unlock()
|
||||
|
||||
for handler in handlers {
|
||||
handler(packet)
|
||||
}
|
||||
private func routeIncomingSearchPacket(_ packet: PacketSearch) -> SearchPacketChannel {
|
||||
searchRouter.dispatchIncomingResponse(packet)
|
||||
}
|
||||
|
||||
private func notifyResultHandlers(_ packet: PacketResult) {
|
||||
@@ -609,6 +614,7 @@ final class ProtocolManager: @unchecked Sendable {
|
||||
case .needDeviceVerification:
|
||||
handshakeComplete = false
|
||||
Self.logger.info("Server requires device verification — approve this device from your other Rosetta app")
|
||||
searchRouter.resetPending()
|
||||
|
||||
Task { @MainActor in
|
||||
self.connectionState = .deviceVerificationRequired
|
||||
|
||||
Reference in New Issue
Block a user