Skip to content

Commit 5f9df78

Browse files
committed
rewrite loop()
1 parent 82fc9b6 commit 5f9df78

File tree

1 file changed

+75
-52
lines changed

1 file changed

+75
-52
lines changed

Loop/Managers/LoopDataManager.swift

Lines changed: 75 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import Foundation
1010
import HealthKit
1111
import LoopKit
1212
import LoopCore
13+
import Combine
1314

1415

1516
final class LoopDataManager {
@@ -33,6 +34,8 @@ final class LoopDataManager {
3334

3435
private let logger: CategoryLogger
3536

37+
private var loopSubscription: AnyCancellable?
38+
3639
// References to registered notification center observers
3740
private var notificationObservers: [Any] = []
3841

@@ -636,57 +639,77 @@ extension LoopDataManager {
636639
/// Executes an analysis of the current data, and recommends an adjustment to the current
637640
/// temporary basal rate.
638641
func loop() {
639-
self.dataAccessQueue.async {
640-
self.logger.default("Loop running")
641-
NotificationCenter.default.post(name: .LoopRunning, object: self)
642+
let updatePublisher = Deferred {
643+
Future<(), Error> { promise in
644+
do {
645+
try self.update()
646+
promise(.success(()))
647+
} catch let error {
648+
promise(.failure(error))
649+
}
650+
}
651+
}
652+
.subscribe(on: dataAccessQueue)
653+
.eraseToAnyPublisher()
642654

643-
self.lastLoopError = nil
644-
let startDate = Date()
655+
let enactBolusPuiblisher = Deferred {
656+
Future<Bool, Error> { promise in
657+
guard self.settings.dosingEnabled, self.settings.microbolusesEnabled else {
658+
return promise(.success(false))
659+
}
660+
self.calculateAndEnactMicroBolusIfNeeded { enacted, error in
661+
if let error = error {
662+
promise(.failure(error))
663+
}
664+
promise(.success(enacted))
665+
}
666+
}
667+
}
668+
.subscribe(on: dataAccessQueue)
669+
.eraseToAnyPublisher()
645670

646-
do {
647-
try self.update()
671+
let setBasalPublisher = Deferred {
672+
Future<(), Error> { promise in
673+
guard self.settings.dosingEnabled else {
674+
return promise(.success(()))
675+
}
648676

649-
if self.settings.dosingEnabled {
650-
self.setRecommendedTempBasal { (error) -> Void in
651-
self.lastLoopError = error
652-
653-
if let error = error {
654-
self.logger.error(error)
655-
} else {
656-
if self.settings.microbolusesEnabled {
657-
self.calculateAndEnactMicroBolusIfNeeded { error in
658-
if let error = error {
659-
self.lastLoopError = error
660-
self.logger.error(error)
661-
} else {
662-
self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow)
663-
}
664-
665-
self.logger.default("Loop ended")
666-
self.notify(forChange: .tempBasal)
667-
668-
return
669-
}
670-
} else {
671-
self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow)
672-
}
673-
}
674-
self.logger.default("Loop ended")
675-
self.notify(forChange: .tempBasal)
677+
self.setRecommendedTempBasal { error in
678+
if let error = error {
679+
promise(.failure(error))
676680
}
677-
678-
// Delay the notification until we know the result of the temp basal
679-
return
680-
} else {
681-
self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow)
681+
promise(.success(()))
682682
}
683-
} catch let error {
684-
self.lastLoopError = error
685-
}
686683

687-
self.logger.default("Loop ended")
688-
self.notify(forChange: .tempBasal)
684+
}
689685
}
686+
.subscribe(on: dataAccessQueue)
687+
.eraseToAnyPublisher()
688+
689+
logger.default("Loop running")
690+
NotificationCenter.default.post(name: .LoopRunning, object: self)
691+
692+
loopSubscription?.cancel()
693+
694+
let startDate = Date()
695+
loopSubscription = updatePublisher
696+
.flatMap { _ in setBasalPublisher }
697+
.flatMap { _ in enactBolusPuiblisher }
698+
.receive(on: dataAccessQueue)
699+
.sink(
700+
receiveCompletion: { completion in
701+
switch completion {
702+
case .finished:
703+
self.loopDidComplete(date: Date(), duration: -startDate.timeIntervalSinceNow)
704+
case let .failure(error):
705+
self.lastLoopError = nil
706+
self.logger.error(error)
707+
}
708+
self.logger.default("Loop ended")
709+
self.notify(forChange: .tempBasal)
710+
},
711+
receiveValue: { _ in }
712+
)
690713
}
691714

692715
/// - Throws:
@@ -1047,38 +1070,38 @@ extension LoopDataManager {
10471070
}
10481071

10491072
/// *This method should only be called from the `dataAccessQueue`*
1050-
private func calculateAndEnactMicroBolusIfNeeded(_ completion: @escaping (_ error: Error?) -> Void) {
1073+
private func calculateAndEnactMicroBolusIfNeeded(_ completion: @escaping (_ enacted: Bool, _ error: Error?) -> Void) {
10511074
dispatchPrecondition(condition: .onQueue(dataAccessQueue))
10521075

10531076
guard settings.dosingEnabled && settings.microbolusesEnabled else {
10541077
logger.debug("Closed loop or microboluses disabled. Cancel microbolus calculation.")
1055-
completion(nil)
1078+
completion(false, nil)
10561079
return
10571080
}
10581081

10591082
let startDate = Date()
10601083

10611084
guard let recommendedBolus = recommendedBolus else {
10621085
logger.debug("No recommended bolus. Cancel microbolus calculation.")
1063-
completion(nil)
1086+
completion(false, nil)
10641087
return
10651088
}
10661089

10671090
guard abs(recommendedBolus.date.timeIntervalSinceNow) < TimeInterval(minutes: 5) else {
1068-
completion(LoopError.recommendationExpired(date: recommendedBolus.date))
1091+
completion(false, LoopError.recommendationExpired(date: recommendedBolus.date))
10691092
return
10701093
}
10711094

10721095
guard let currentBasalRate = basalRateScheduleApplyingOverrideHistory?.value(at: startDate) else {
10731096
logger.debug("Basal rates not configured. Cancel microbolus calculation.")
1074-
completion(nil)
1097+
completion(false, nil)
10751098
return
10761099
}
10771100

10781101
let insulinReq = recommendedBolus.recommendation.amount
10791102
guard insulinReq > 0 else {
10801103
logger.debug("No microbolus needed.")
1081-
completion(nil)
1104+
completion(false, nil)
10821105
return
10831106
}
10841107

@@ -1095,10 +1118,10 @@ extension LoopDataManager {
10951118
self.delegate?.loopDataManager(self, didRecommendMicroBolus: recomendation) { [weak self] error in
10961119
if let error = error {
10971120
self?.logger.debug("Microbolus failed: \(error.localizedDescription)")
1098-
completion(error)
1121+
completion(false, error)
10991122
} else {
11001123
self?.logger.debug("Microbolus enacted")
1101-
completion(nil)
1124+
completion(true, nil)
11021125
}
11031126
}
11041127
}

0 commit comments

Comments
 (0)