ForecastSetup.swift 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import CoreData
  2. import Foundation
  3. extension Home.StateModel {
  4. // Asynchronously preprocess Forecast data in a background thread
  5. func preprocessForecastData() async -> [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])] {
  6. // Get the Determination ID on the main context
  7. guard let id = await viewContext.perform({ self.enactedAndNonEnactedDeterminations.first?.objectID }) else {
  8. return []
  9. }
  10. // Get the Forecast IDs for the Determination ID
  11. // Here we can safely use a background context since we are using the NSManagedObjectID
  12. let forecastIDs = await determinationStorage.getForecastIDs(for: id, in: taskContext)
  13. var result: [(id: UUID, forecastID: NSManagedObjectID, forecastValueIDs: [NSManagedObjectID])] = []
  14. // Use a task group to fetch Forecast VALUE IDs concurrently
  15. await withTaskGroup(of: (UUID, NSManagedObjectID, [NSManagedObjectID]).self) { group in
  16. for forecastID in forecastIDs {
  17. group.addTask {
  18. // Fetch forecast value IDs asynchronously (but outside of perform)
  19. let forecastValueIDs = await self.determinationStorage.getForecastValueIDs(
  20. for: forecastID,
  21. in: self.taskContext
  22. )
  23. return (UUID(), forecastID, forecastValueIDs)
  24. }
  25. }
  26. // Collect the results from the task group
  27. for await (uuid, forecastID, forecastValueIDs) in group {
  28. result.append((id: uuid, forecastID: forecastID, forecastValueIDs: forecastValueIDs))
  29. }
  30. }
  31. return result
  32. }
  33. // Update forecast data and UI on the main thread
  34. @MainActor func updateForecastData() async {
  35. // Preprocess forecast data on a background thread
  36. let forecastDataIDs = await preprocessForecastData()
  37. // Use an Array of Int instead of ForecastValues to be able to pass values thread safe
  38. var allForecastValues = [[Int]]()
  39. var preprocessedData = [(id: UUID, forecast: Forecast, forecastValue: ForecastValue)]()
  40. // Use a task group to fetch forecast values concurrently
  41. await withTaskGroup(of: (UUID, Forecast?, [ForecastValue]).self) { group in
  42. for data in forecastDataIDs {
  43. group.addTask {
  44. await self.determinationStorage
  45. .fetchForecastObjects(
  46. for: data,
  47. in: self.viewContext
  48. ) // This directly returns NSManagedobjects on the Main Thread
  49. }
  50. }
  51. // Collect the results from the task group
  52. for await (id, forecast, forecastValues) in group {
  53. guard let forecast = forecast, !forecastValues.isEmpty else { continue }
  54. // Extract only the 'value' from ForecastValue on the main thread
  55. let forecastValueInts = forecastValues
  56. .compactMap { Int($0.value) }
  57. allForecastValues.append(forecastValueInts)
  58. preprocessedData.append(contentsOf: forecastValues.map { (id: id, forecast: forecast, forecastValue: $0) })
  59. }
  60. }
  61. // Update Array on the Main Thread
  62. self.preprocessedData = preprocessedData
  63. // Ensure there are forecast values to process
  64. guard !allForecastValues.isEmpty else {
  65. minForecast = []
  66. maxForecast = []
  67. return
  68. }
  69. minCount = max(12, allForecastValues.map(\.count).min() ?? 0)
  70. guard minCount > 0 else { return }
  71. // Copy allForecastValues to a local constant for thread safety
  72. let localAllForecastValues = allForecastValues
  73. // Calculate min and max forecast values in a background task
  74. let (minResult, maxResult) = await Task.detached {
  75. let minForecast = (0 ..< self.minCount).map { index in
  76. localAllForecastValues.compactMap { $0.indices.contains(index) ? $0[index] : nil }.min() ?? 0
  77. }
  78. let maxForecast = (0 ..< self.minCount).map { index in
  79. localAllForecastValues.compactMap { $0.indices.contains(index) ? $0[index] : nil }.max() ?? 0
  80. }
  81. return (minForecast, maxForecast)
  82. }.value
  83. // Update the properties on the main thread
  84. minForecast = minResult
  85. maxForecast = maxResult
  86. }
  87. }