Phase 1.14.c:LobbyZipUpgrader 加 0…1 progress 回调

- upgradeIfNeeded 新增 onProgress: @escaping @Sendable (Double) -> Void 参数,
  默认 no-op 兼容仅做版本对比的烟雾测试
- 新增文件内私有 DownloadProgressDelegate(URLSessionDownloadDelegate
  + @unchecked Sendable),在 didWriteData 回调里把 totalBytesWritten /
  totalBytesExpectedToWrite 换算成 0…1 上报,clamp 到 [0,1]
- 通过 session.download(from:delegate:) 注入;下载完成后兜底报 1.0
  (部分 server 不发 Content-Length 或末尾片段晚到,避免进度条停在 99%)
- RootViewController 烟雾测试用 ProgressTicker 把回调节流到 10% 阶梯打印,
  避免几百行刷屏(NSLock 保护单 Int 状态,@unchecked Sendable)
- BuildProject 通过

Plan 进度已勾选(§5 Phase 1.14.c + §8)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
joywayer
2026-06-22 02:46:11 +08:00
co-authored by Claude Opus 4.7
parent ba34854133
commit f3c66b670d
3 changed files with 86 additions and 10 deletions
@@ -44,9 +44,15 @@ public actor LobbyZipUpgrader {
/// game_version version.xml + + rename
///
/// - Parameter resolved: VersionResolver.resolve(...)
/// - Parameters:
/// - resolved: VersionResolver.resolve(...)
/// - onProgress: 0.01.0 URLSession delegate 线
/// UI hop MainActor no-op
/// - Returns: `.noop` `.upgraded(from:to:)`
public func upgradeIfNeeded(resolved: ResolvedVersion) async throws -> Outcome {
public func upgradeIfNeeded(
resolved: ResolvedVersion,
onProgress: @escaping @Sendable (Double) -> Void = { _ in }
) async throws -> Outcome {
let localGameVersion = LocalVersionReader.localGameVersion
guard resolved.gameVersion > localGameVersion,
let zipURLString = resolved.gameZip,
@@ -55,11 +61,15 @@ public actor LobbyZipUpgrader {
return .noop
}
// 1. tmpURLSession
// 1. tmpURLSession delegate 01
let progressDelegate = DownloadProgressDelegate(onProgress: onProgress)
let downloadedURL: URL
let response: URLResponse
do {
(downloadedURL, response) = try await session.download(from: zipURL)
(downloadedURL, response) = try await session.download(
from: zipURL,
delegate: progressDelegate
)
} catch {
throw UpgradeError.downloadFailed(error)
}
@@ -67,6 +77,9 @@ public actor LobbyZipUpgrader {
!(200..<300).contains(http.statusCode) {
throw UpgradeError.httpStatus(http.statusCode)
}
// 1.0 server Content-Length
// 99%
onProgress(1.0)
// 2. staging
let staging = SandboxPaths.caches
@@ -99,3 +112,37 @@ public actor LobbyZipUpgrader {
return .upgraded(from: localGameVersion, to: resolved.gameVersion)
}
}
/// URLSession `didWriteData` byte 01
///
/// LobbyZipUpgrader API
/// `@unchecked Sendable`URLSessionDownloadDelegate Sendable
/// `@Sendable`
private final class DownloadProgressDelegate: NSObject,
URLSessionDownloadDelegate,
@unchecked Sendable {
private let onProgress: @Sendable (Double) -> Void
init(onProgress: @escaping @Sendable (Double) -> Void) {
self.onProgress = onProgress
super.init()
}
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didWriteData bytesWritten: Int64,
totalBytesWritten: Int64,
totalBytesExpectedToWrite: Int64) {
guard totalBytesExpectedToWrite > 0 else { return }
let p = Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)
onProgress(min(max(p, 0), 1))
}
/// async download API
///
func urlSession(_ session: URLSession,
downloadTask: URLSessionDownloadTask,
didFinishDownloadingTo location: URL) {
// no-op
}
}