Просмотр исходного кода

Compute temp basal bars in one main-actor pass

prepareTempBasals was quadratic over a day of pump events and read
viewContext-bound objects on a background executor. Snapshot the fields
once on the main actor and build the bars in one linear sweep; the
unstructured task and its stale-assignment race go away too.
Marvin Polscheit 2 дней назад
Родитель
Сommit
f980f67bea

+ 28 - 23
Trio/Sources/Modules/Home/View/Chart/ChartElements/BasalChart.swift

@@ -27,7 +27,7 @@ extension MainChartView {
                 drawSuspensions()
             }.onChange(of: state.tempBasals) {
                 calculateBasals()
-                calculateTempBasalsInBackground()
+                calculateTempBasals()
             }
             .onChange(of: state.maxBasal) {
                 calculateBasals()
@@ -137,34 +137,39 @@ extension MainChartView {
 // MARK: - Calculation
 
 extension MainChartView {
-    func calculateTempBasalsInBackground() {
-        Task {
-            let basals = await prepareTempBasals()
-            await MainActor.run {
-                preparedTempBasals = basals
-            }
-        }
-    }
-
-    func prepareTempBasals() async -> [(start: Date, end: Date, rate: Double)] {
+    @MainActor func calculateTempBasals() {
         let now = Date()
-        let tempBasals = state.tempBasals
+        let suspensionTimes = state.suspendAndResumeEvents.compactMap(\.timestamp)
 
-        return tempBasals.compactMap { temp -> (start: Date, end: Date, rate: Double)? in
-            let duration = temp.tempBasal?.duration ?? 0
-            let timestamp = temp.timestamp ?? Date()
-            let end = timestamp + duration.minutes
-            let isInsulinSuspended = state.suspendAndResumeEvents
-                .contains { $0.timestamp ?? now >= timestamp && $0.timestamp ?? now <= end }
+        // Snapshot the managed-object fields once; plain values from here on.
+        let events = state.tempBasals.map {
+            (timestamp: $0.timestamp, duration: $0.tempBasal?.duration ?? 0, rate: $0.tempBasal?.rate)
+        }
+
+        var prepared = [(start: Date, end: Date, rate: Double)]()
+        prepared.reserveCapacity(events.count)
 
-            let rate = Double(truncating: temp.tempBasal?.rate ?? Decimal.zero as NSDecimalNumber) * (isInsulinSuspended ? 0 : 1)
+        for (index, event) in events.enumerated() {
+            let timestamp = event.timestamp ?? now
+            let end = timestamp + event.duration.minutes
+            let isInsulinSuspended = suspensionTimes.contains { $0 >= timestamp && $0 <= end }
+            let rate = Double(truncating: event.rate ?? 0) * (isInsulinSuspended ? 0 : 1)
 
-            // Check if there's a subsequent temp basal to determine the end time
-            guard let nextTemp = state.tempBasals.first(where: { $0.timestamp ?? .distantPast > timestamp }) else {
-                return (timestamp, end, rate)
+            // A bar ends where the next later-starting temp basal begins,
+            // else at its own scheduled end.
+            var next = index + 1
+            while next < events.count {
+                if let nextStart = events[next].timestamp, nextStart > timestamp { break }
+                next += 1
+            }
+            if next < events.count, let nextStart = events[next].timestamp {
+                prepared.append((timestamp, nextStart, rate))
+            } else {
+                prepared.append((timestamp, end, rate))
             }
-            return (timestamp, nextTemp.timestamp ?? Date(), rate)
         }
+
+        preparedTempBasals = prepared
     }
 
     func findRegularBasalPoints(

+ 1 - 1
Trio/Sources/Modules/Home/View/Chart/MainChartView.swift

@@ -96,7 +96,7 @@ struct MainChartView: View {
                             if !mainChartHasInitialized {
                                 scroller.scrollTo("MainChart", anchor: .trailing)
                                 state.updateStartEndMarkers()
-                                calculateTempBasalsInBackground()
+                                calculateTempBasals()
                                 mainChartHasInitialized = true
                             }
                         }