TEMP: on-screen diagnostic overlay for the watch hang
Records recent ExerciseProgressView lifecycle/mutation events (init count, page changes, bridge updates, phase starts) and shows them as a green overlay so they can be read off a frozen screen on-device. To be reverted once the hang is diagnosed.
This commit is contained in:
@@ -8,6 +8,18 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import WatchKit
|
import WatchKit
|
||||||
|
|
||||||
|
// DIAGNOSTIC (temporary): records recent lifecycle/mutation events so the on-screen
|
||||||
|
// overlay can show what ran right before a freeze. Remove once the hang is solved.
|
||||||
|
enum PVDiag {
|
||||||
|
nonisolated(unsafe) static var initCount = 0
|
||||||
|
nonisolated(unsafe) static var lines: [String] = []
|
||||||
|
static func add(_ s: String) {
|
||||||
|
print("⌚️PV \(s)")
|
||||||
|
lines.append(s)
|
||||||
|
if lines.count > 7 { lines.removeFirst(lines.count - 7) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Runs a single exercise as a horizontally-paged flow, mirroring the iPhone's:
|
/// Runs a single exercise as a horizontally-paged flow, mirroring the iPhone's:
|
||||||
///
|
///
|
||||||
/// [Ready] → Work₁ → Rest₁ → Work₂ → … → Workₙ → Finish
|
/// [Ready] → Work₁ → Rest₁ → Work₂ → … → Workₙ → Finish
|
||||||
@@ -71,6 +83,8 @@ struct ExerciseProgressView: View {
|
|||||||
let completed = min(max(0, log?.currentStateIndex ?? 0), sets - 1)
|
let completed = min(max(0, log?.currentStateIndex ?? 0), sets - 1)
|
||||||
let resume = ready ? 0 : base + completed * 2
|
let resume = ready ? 0 : base + completed * 2
|
||||||
_currentPage = State(initialValue: debugInitialPage ?? resume)
|
_currentPage = State(initialValue: debugInitialPage ?? resume)
|
||||||
|
PVDiag.initCount += 1
|
||||||
|
PVDiag.add("init: ready=\(ready) page=\(debugInitialPage ?? resume) sets=\(sets) duration=\(LoadType(rawValue: log?.loadType ?? -1) == .duration)")
|
||||||
}
|
}
|
||||||
|
|
||||||
private var log: WorkoutLogDocument? {
|
private var log: WorkoutLogDocument? {
|
||||||
@@ -152,6 +166,20 @@ struct ExerciseProgressView: View {
|
|||||||
.padding(.bottom, 2)
|
.padding(.bottom, 2)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// DIAGNOSTIC overlay (temporary): shows recent events; readable off a frozen screen.
|
||||||
|
.overlay(alignment: .top) {
|
||||||
|
VStack(alignment: .leading, spacing: 0) {
|
||||||
|
Text("init#\(PVDiag.initCount)").bold()
|
||||||
|
ForEach(Array(PVDiag.lines.enumerated()), id: \.offset) { _, line in
|
||||||
|
Text(line)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.font(.system(size: 8, design: .monospaced))
|
||||||
|
.foregroundStyle(.green)
|
||||||
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
.background(.black.opacity(0.65))
|
||||||
|
.allowsHitTesting(false)
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
Button {
|
Button {
|
||||||
@@ -166,6 +194,7 @@ struct ExerciseProgressView: View {
|
|||||||
Button("Continue", role: .cancel) { }
|
Button("Continue", role: .cancel) { }
|
||||||
}
|
}
|
||||||
.onChange(of: currentPage) { _, newPage in
|
.onChange(of: currentPage) { _, newPage in
|
||||||
|
PVDiag.add("currentPage -> \(newPage)")
|
||||||
// Swiping all the way back to the Ready page wipes the run; any other page
|
// Swiping all the way back to the Ready page wipes the run; any other page
|
||||||
// records forward progress.
|
// records forward progress.
|
||||||
if showsReady && newPage == 0 {
|
if showsReady && newPage == 0 {
|
||||||
@@ -264,6 +293,7 @@ struct ExerciseProgressView: View {
|
|||||||
|
|
||||||
/// Flip a not-yet-started exercise to in-progress the moment the user taps Start.
|
/// Flip a not-yet-started exercise to in-progress the moment the user taps Start.
|
||||||
private func beginExercise() {
|
private func beginExercise() {
|
||||||
|
PVDiag.add("beginExercise -> onChange (bridge.update)")
|
||||||
guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return }
|
guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return }
|
||||||
guard doc.logs[i].status == WorkoutStatus.notStarted.rawValue else { return }
|
guard doc.logs[i].status == WorkoutStatus.notStarted.rawValue else { return }
|
||||||
doc.logs[i].status = WorkoutStatus.inProgress.rawValue
|
doc.logs[i].status = WorkoutStatus.inProgress.rawValue
|
||||||
@@ -307,6 +337,7 @@ struct ExerciseProgressView: View {
|
|||||||
guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return }
|
guard let i = doc.logs.firstIndex(where: { $0.id == logID }) else { return }
|
||||||
guard reached > doc.logs[i].currentStateIndex else { return }
|
guard reached > doc.logs[i].currentStateIndex else { return }
|
||||||
|
|
||||||
|
PVDiag.add("recordProgress reached=\(reached) -> onChange (bridge.update)")
|
||||||
doc.logs[i].currentStateIndex = reached
|
doc.logs[i].currentStateIndex = reached
|
||||||
doc.logs[i].status = WorkoutStatus.inProgress.rawValue
|
doc.logs[i].status = WorkoutStatus.inProgress.rawValue
|
||||||
doc.logs[i].completed = false
|
doc.logs[i].completed = false
|
||||||
@@ -324,6 +355,7 @@ struct ExerciseProgressView: View {
|
|||||||
guard log.currentStateIndex != 0
|
guard log.currentStateIndex != 0
|
||||||
|| log.status != WorkoutStatus.notStarted.rawValue
|
|| log.status != WorkoutStatus.notStarted.rawValue
|
||||||
|| log.completed else { return }
|
|| log.completed else { return }
|
||||||
|
PVDiag.add("resetExercise -> onChange (bridge.update)")
|
||||||
doc.logs[i].currentStateIndex = 0
|
doc.logs[i].currentStateIndex = 0
|
||||||
doc.logs[i].status = WorkoutStatus.notStarted.rawValue
|
doc.logs[i].status = WorkoutStatus.notStarted.rawValue
|
||||||
doc.logs[i].completed = false
|
doc.logs[i].completed = false
|
||||||
@@ -548,6 +580,7 @@ private struct WorkPhaseView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func restart() {
|
private func restart() {
|
||||||
|
PVDiag.add("work.restart set=\(setNumber) (beep)")
|
||||||
startDate = Date()
|
startDate = Date()
|
||||||
WorkoutHaptic.start.play()
|
WorkoutHaptic.start.play()
|
||||||
}
|
}
|
||||||
@@ -591,6 +624,7 @@ private struct CountdownPhaseView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func start() {
|
private func start() {
|
||||||
|
PVDiag.add("countdown.start '\(header)' seconds=\(seconds) (beep)")
|
||||||
startDate = Date()
|
startDate = Date()
|
||||||
endDate = startDate.addingTimeInterval(Double(max(1, seconds)))
|
endDate = startDate.addingTimeInterval(Double(max(1, seconds)))
|
||||||
lastPingSecond = Int.max
|
lastPingSecond = Int.max
|
||||||
@@ -606,6 +640,7 @@ private struct CountdownPhaseView: View {
|
|||||||
if remaining <= 0 {
|
if remaining <= 0 {
|
||||||
// Time's up — final cue and slide on. If the wrist was down the timer may have
|
// Time's up — final cue and slide on. If the wrist was down the timer may have
|
||||||
// stalled; this then fires on the first tick once the app gets runtime again.
|
// stalled; this then fires on the first tick once the app gets runtime again.
|
||||||
|
PVDiag.add("countdown.finished '\(header)' -> advance")
|
||||||
didFinish = true
|
didFinish = true
|
||||||
WorkoutHaptic.stop.play()
|
WorkoutHaptic.stop.play()
|
||||||
onFinished()
|
onFinished()
|
||||||
|
|||||||
Reference in New Issue
Block a user