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

First cut implementation for makeProfile

Sam King 1 год назад
Родитель
Сommit
44de1dd869

+ 32 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -233,6 +233,9 @@
 		38FEF3FC2737E53800574A46 /* MainStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF3FB2737E53800574A46 /* MainStateModel.swift */; };
 		38FEF3FE2738083E00574A46 /* CGMProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF3FD2738083E00574A46 /* CGMProvider.swift */; };
 		38FEF413273B317A00574A46 /* HKUnit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38FEF412273B317A00574A46 /* HKUnit.swift */; };
+		3B5CD1EC2D4912A600CE213C /* OpenAPSSwift.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5CD1E92D4912A600CE213C /* OpenAPSSwift.swift */; };
+		3B5CD1ED2D4912A600CE213C /* JSONBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B5CD1EA2D4912A600CE213C /* JSONBridge.swift */; };
+		3B5CD1EF2D4915BF00CE213C /* OpenAPSKit in Frameworks */ = {isa = PBXBuildFile; productRef = 3B5CD1EE2D4915BF00CE213C /* OpenAPSKit */; };
 		45252C95D220E796FDB3B022 /* ConfigEditorDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8A87AA037BD079BA3528BA /* ConfigEditorDataFlow.swift */; };
 		45717281F743594AA9D87191 /* ConfigEditorRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 920DDB21E5D0EB813197500D /* ConfigEditorRootView.swift */; };
 		5075C1608E6249A51495C422 /* TargetsEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */; };
@@ -936,6 +939,8 @@
 		38FEF3FB2737E53800574A46 /* MainStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainStateModel.swift; sourceTree = "<group>"; };
 		38FEF3FD2738083E00574A46 /* CGMProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CGMProvider.swift; sourceTree = "<group>"; };
 		38FEF412273B317A00574A46 /* HKUnit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HKUnit.swift; sourceTree = "<group>"; };
+		3B5CD1E92D4912A600CE213C /* OpenAPSSwift.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAPSSwift.swift; sourceTree = "<group>"; };
+		3B5CD1EA2D4912A600CE213C /* JSONBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONBridge.swift; sourceTree = "<group>"; };
 		3BDEA2DC60EDE0A3CA54DC73 /* TargetsEditorProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = TargetsEditorProvider.swift; sourceTree = "<group>"; };
 		3BF768BD6264FF7D71D66767 /* NightscoutConfigProvider.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = NightscoutConfigProvider.swift; sourceTree = "<group>"; };
 		3F60E97100041040446F44E7 /* PumpConfigStateModel.swift */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.swift; path = PumpConfigStateModel.swift; sourceTree = "<group>"; };
@@ -1294,6 +1299,7 @@
 				B958F1B72BA0711600484851 /* MKRingProgressView in Frameworks */,
 				CE95BF5B2BA770C300DC3DE3 /* LoopKit.framework in Frameworks */,
 				38B17B6625DD90E0005CAE3D /* SwiftDate in Frameworks */,
+				3B5CD1EF2D4915BF00CE213C /* OpenAPSKit in Frameworks */,
 				3833B46D26012030003021B3 /* Algorithms in Frameworks */,
 				CEB434FD28B90B7C00B70274 /* SwiftCharts in Frameworks */,
 				CE95BF5F2BA7715800DC3DE3 /* MockKit.framework in Frameworks */,
@@ -1834,6 +1840,7 @@
 				38192E06261BA9960094D973 /* FetchTreatmentsManager.swift */,
 				3856933F270B57A00002C50D /* CGM */,
 				38A504F625DDA0E200C5B9E8 /* Extensions */,
+				3B5CD1EB2D4912A600CE213C /* OpenAPSSwift */,
 				388E5A5825B6F0070019842D /* OpenAPS */,
 				38A0362725ECF05300FCBB52 /* Storage */,
 			);
@@ -2288,6 +2295,15 @@
 			path = FreeAPSTests;
 			sourceTree = "<group>";
 		};
+		3B5CD1EB2D4912A600CE213C /* OpenAPSSwift */ = {
+			isa = PBXGroup;
+			children = (
+				3B5CD1E92D4912A600CE213C /* OpenAPSSwift.swift */,
+				3B5CD1EA2D4912A600CE213C /* JSONBridge.swift */,
+			);
+			path = OpenAPSSwift;
+			sourceTree = "<group>";
+		};
 		4E8C7B59F8065047ECE20965 /* View */ = {
 			isa = PBXGroup;
 			children = (
@@ -3124,6 +3140,7 @@
 				38DF1788276FC8C400B3528F /* SwiftMessages */,
 				CEB434FC28B90B7C00B70274 /* SwiftCharts */,
 				B958F1B62BA0711600484851 /* MKRingProgressView */,
+				3B5CD1EE2D4915BF00CE213C /* OpenAPSKit */,
 			);
 			productName = FreeAPS;
 			productReference = 388E595825AD948C0019842D /* FreeAPS.app */;
@@ -3266,6 +3283,7 @@
 				38DF1787276FC8C300B3528F /* XCRemoteSwiftPackageReference "SwiftMessages" */,
 				CEB434FB28B90B7C00B70274 /* XCRemoteSwiftPackageReference "SwiftCharts" */,
 				B958F1B52BA0711600484851 /* XCRemoteSwiftPackageReference "MKRingProgressView" */,
+				3B5CD1E82D49126800CE213C /* XCLocalSwiftPackageReference "../OpenAPSKit" */,
 			);
 			productRefGroup = 388E595925AD948C0019842D /* Products */;
 			projectDirPath = "";
@@ -3768,6 +3786,8 @@
 				38E4453D274E411700EC9A94 /* Disk+Errors.swift in Sources */,
 				58D08B3A2C8DFECD00AA37D3 /* TempTargets.swift in Sources */,
 				38E98A2325F52C9300C0CED0 /* Signpost.swift in Sources */,
+				3B5CD1EC2D4912A600CE213C /* OpenAPSSwift.swift in Sources */,
+				3B5CD1ED2D4912A600CE213C /* JSONBridge.swift in Sources */,
 				CE7CA3542A064973004BE681 /* TempPresetsIntentRequest.swift in Sources */,
 				58A3D5442C96DE11003F90FC /* TempTargetStored+Helper.swift in Sources */,
 				58A3D5532C96EFA8003F90FC /* TempTargetRunStored+CoreDataClass.swift in Sources */,
@@ -4577,6 +4597,13 @@
 		};
 /* End XCConfigurationList section */
 
+/* Begin XCLocalSwiftPackageReference section */
+		3B5CD1E82D49126800CE213C /* XCLocalSwiftPackageReference "../OpenAPSKit" */ = {
+			isa = XCLocalSwiftPackageReference;
+			relativePath = ../OpenAPSKit;
+		};
+/* End XCLocalSwiftPackageReference section */
+
 /* Begin XCRemoteSwiftPackageReference section */
 		3811DE0E25C9D37700A708ED /* XCRemoteSwiftPackageReference "Swinject" */ = {
 			isa = XCRemoteSwiftPackageReference;
@@ -4654,6 +4681,11 @@
 			package = 38B17B6425DD90E0005CAE3D /* XCRemoteSwiftPackageReference "SwiftDate" */;
 			productName = SwiftDate;
 		};
+		3B5CD1EE2D4915BF00CE213C /* OpenAPSKit */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = 3B5CD1E82D49126800CE213C /* XCLocalSwiftPackageReference "../OpenAPSKit" */;
+			productName = OpenAPSKit;
+		};
 		B958F1B62BA0711600484851 /* MKRingProgressView */ = {
 			isa = XCSwiftPackageProductDependency;
 			package = B958F1B52BA0711600484851 /* XCRemoteSwiftPackageReference "MKRingProgressView" */;

+ 64 - 1
FreeAPS/Sources/APS/OpenAPS/OpenAPS.swift

@@ -2,6 +2,7 @@ import Combine
 import CoreData
 import Foundation
 import JavaScriptCore
+import OpenAPSKit
 
 final class OpenAPS {
     private let jsWorker = JavaScriptWorker()
@@ -13,6 +14,8 @@ final class OpenAPS {
 
     let jsonConverter = JSONConverter()
 
+    private let enableNativeOref = false // TODO: Replace with a default-on setting
+
     init(storage: FileStorage) {
         self.storage = storage
     }
@@ -679,7 +682,7 @@ final class OpenAPS {
         }
     }
 
-    private func makeProfile(
+    private func makeProfileJavascript(
         preferences: JSON,
         pumpSettings: JSON,
         bgTargets: JSON,
@@ -715,6 +718,66 @@ final class OpenAPS {
         }
     }
 
+    private func makeProfile(
+        preferences: JSON,
+        pumpSettings: JSON,
+        bgTargets: JSON,
+        basalProfile: JSON,
+        isf: JSON,
+        carbRatio: JSON,
+        tempTargets: JSON,
+        model: JSON,
+        autotune: JSON,
+        freeaps: JSON
+    ) async throws -> RawJSON {
+        // TODO: Compare exceptions as well
+        let startJavascriptAt = Date()
+        let jsJson = try await makeProfileJavascript(
+            preferences: preferences,
+            pumpSettings: pumpSettings,
+            bgTargets: bgTargets,
+            basalProfile: basalProfile,
+            isf: isf,
+            carbRatio: carbRatio,
+            tempTargets: tempTargets,
+            model: model,
+            autotune: autotune,
+            freeaps: freeaps
+        )
+        let javascriptDuration = Date().timeIntervalSince(startJavascriptAt)
+
+        // Important: we want to make sure that this flag ensures that none
+        // of the native code runs
+        guard enableNativeOref else {
+            return jsJson
+        }
+
+        let startNativeAt = Date()
+        let nativeJson = OpenAPSSwift.makeProfile(
+            preferences: preferences,
+            pumpSettings: pumpSettings,
+            bgTargets: bgTargets,
+            basalProfile: basalProfile,
+            isf: isf,
+            carbRatio: carbRatio,
+            tempTargets: tempTargets,
+            model: model,
+            autotune: autotune,
+            freeaps: freeaps
+        )
+        let nativeDuration = Date().timeIntervalSince(startNativeAt)
+
+        OpenAPSKit.JSONCompare.logDifferences(
+            label: "makeProfile",
+            native: nativeJson,
+            nativeRuntime: nativeDuration,
+            javascript: jsJson,
+            javascriptRuntime: javascriptDuration
+        )
+
+        return jsJson
+    }
+
     private func loadJSON(name: String) -> String {
         try! String(contentsOf: Foundation.Bundle.main.url(forResource: "json/\(name)", withExtension: "json")!)
     }

+ 66 - 0
FreeAPS/Sources/APS/OpenAPSSwift/JSONBridge.swift

@@ -0,0 +1,66 @@
+import Foundation
+import OpenAPSKit
+
+enum JSONError: Error {
+    case invalidString
+    case decodingFailed(Error)
+    case encodingFailed
+}
+
+enum JSONBridge {
+    static func preferences(from: JSON) throws -> OpenAPSKit.Preferences {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func pumpSettings(from: JSON) throws -> OpenAPSKit.PumpSettings {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func bgTargets(from: JSON) throws -> OpenAPSKit.BGTargets {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func basalProfile(from: JSON) throws -> [OpenAPSKit.BasalProfileEntry] {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func insulinSensitivities(from: JSON) throws -> OpenAPSKit.InsulinSensitivities {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func carbRatios(from: JSON) throws -> OpenAPSKit.CarbRatios {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func tempTargets(from: JSON) throws -> [OpenAPSKit.TempTarget] {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func model(from: JSON) -> String {
+        from.rawJSON
+    }
+
+    static func autotune(from: JSON) throws -> OpenAPSKit.Autotune? {
+        guard from.rawJSON != RawJSON.null else { return nil }
+        return try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func freeapsSettings(from: JSON) throws -> OpenAPSKit.FreeAPSSettings {
+        try JSONBridge.from(string: from.rawJSON)
+    }
+
+    static func from<T: Decodable>(string: String) throws -> T {
+        guard let data = string.data(using: .utf8) else {
+            throw JSONError.invalidString
+        }
+        return try JSONCoding.decoder.decode(T.self, from: data)
+    }
+
+    static func to<T: Encodable>(_ value: T) throws -> String {
+        let data = try JSONCoding.encoder.encode(value)
+        guard let string = String(data: data, encoding: .utf8) else {
+            throw JSONError.encodingFailed
+        }
+        return string
+    }
+}

+ 48 - 0
FreeAPS/Sources/APS/OpenAPSSwift/OpenAPSSwift.swift

@@ -0,0 +1,48 @@
+import Foundation
+import OpenAPSKit
+
+struct OpenAPSSwift {
+    static func makeProfile(
+        preferences: JSON,
+        pumpSettings: JSON,
+        bgTargets: JSON,
+        basalProfile: JSON,
+        isf: JSON,
+        carbRatio: JSON,
+        tempTargets: JSON,
+        model: JSON,
+        autotune: JSON,
+        freeaps: JSON
+    ) -> RawJSON {
+        do {
+            let preferences = try JSONBridge.preferences(from: preferences)
+            let pumpSettings = try JSONBridge.pumpSettings(from: pumpSettings)
+            let bgTargets = try JSONBridge.bgTargets(from: bgTargets)
+            let basalProfile = try JSONBridge.basalProfile(from: basalProfile)
+            let isf = try JSONBridge.insulinSensitivities(from: isf)
+            let carbRatio = try JSONBridge.carbRatios(from: carbRatio)
+            let tempTargets = try JSONBridge.tempTargets(from: tempTargets)
+            let model = JSONBridge.model(from: model)
+            let autotune = try JSONBridge.autotune(from: autotune)
+            let freeaps = try JSONBridge.freeapsSettings(from: freeaps)
+
+            let profile = try OpenAPSKit.ProfileGenerator.generate(
+                pumpSettings: pumpSettings,
+                bgTargets: bgTargets,
+                basalProfile: basalProfile,
+                isf: isf,
+                preferences: preferences,
+                carbRatios: carbRatio,
+                tempTargets: tempTargets,
+                model: model,
+                autotune: autotune,
+                freeaps: freeaps
+            )
+
+            return try JSONBridge.to(profile)
+        } catch {
+            warning(.openAPS, "OpenAPSSwift exception \(error)")
+            return .null
+        }
+    }
+}