perf: parallelize regional trip generation with async let
Run East/Central/West regional trips and cross-country routes concurrently instead of sequentially, reducing wall-clock time. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -122,7 +122,7 @@ final class SuggestedTripsGenerator {
|
|||||||
|
|
||||||
// Move heavy computation to background task
|
// Move heavy computation to background task
|
||||||
let result = await Task.detached(priority: .userInitiated) {
|
let result = await Task.detached(priority: .userInitiated) {
|
||||||
Self.generateTripsInBackground(
|
await Self.generateTripsInBackground(
|
||||||
games: games,
|
games: games,
|
||||||
stadiums: stadiums,
|
stadiums: stadiums,
|
||||||
teams: teams,
|
teams: teams,
|
||||||
@@ -145,92 +145,117 @@ final class SuggestedTripsGenerator {
|
|||||||
|
|
||||||
// MARK: - Background Trip Generation
|
// MARK: - Background Trip Generation
|
||||||
|
|
||||||
/// Performs heavy trip generation computation off the main actor
|
/// Generates trips for a single region (single-sport + multi-sport)
|
||||||
|
private nonisolated static func generateRegionTrips(
|
||||||
|
region: Region,
|
||||||
|
games: [Game],
|
||||||
|
stadiums: [Stadium],
|
||||||
|
stadiumsById: [String: Stadium],
|
||||||
|
teamsById: [String: Team],
|
||||||
|
startDate: Date,
|
||||||
|
endDate: Date
|
||||||
|
) -> [SuggestedTrip] {
|
||||||
|
let regionStadiumIds = Set(
|
||||||
|
stadiums
|
||||||
|
.filter { $0.region == region }
|
||||||
|
.map { $0.id }
|
||||||
|
)
|
||||||
|
|
||||||
|
let regionGames = games.filter { regionStadiumIds.contains($0.stadiumId) }
|
||||||
|
guard !regionGames.isEmpty else { return [] }
|
||||||
|
|
||||||
|
let planningEngine = TripPlanningEngine()
|
||||||
|
var trips: [SuggestedTrip] = []
|
||||||
|
|
||||||
|
// Single sport trip
|
||||||
|
if let singleSportTrip = generateRegionalTrip(
|
||||||
|
games: regionGames,
|
||||||
|
region: region,
|
||||||
|
singleSport: true,
|
||||||
|
stadiums: stadiumsById,
|
||||||
|
teams: teamsById,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
planningEngine: planningEngine
|
||||||
|
) {
|
||||||
|
trips.append(singleSportTrip)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multi-sport trip
|
||||||
|
if let multiSportTrip = generateRegionalTrip(
|
||||||
|
games: regionGames,
|
||||||
|
region: region,
|
||||||
|
singleSport: false,
|
||||||
|
stadiums: stadiumsById,
|
||||||
|
teams: teamsById,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
planningEngine: planningEngine
|
||||||
|
) {
|
||||||
|
trips.append(multiSportTrip)
|
||||||
|
} else if let fallbackTrip = generateRegionalTrip(
|
||||||
|
games: regionGames,
|
||||||
|
region: region,
|
||||||
|
singleSport: true,
|
||||||
|
stadiums: stadiumsById,
|
||||||
|
teams: teamsById,
|
||||||
|
startDate: startDate,
|
||||||
|
endDate: endDate,
|
||||||
|
excludingSport: trips.last?.sports.first,
|
||||||
|
planningEngine: planningEngine
|
||||||
|
) {
|
||||||
|
trips.append(fallbackTrip)
|
||||||
|
}
|
||||||
|
|
||||||
|
return trips
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs heavy trip generation computation off the main actor.
|
||||||
|
/// Runs all 3 regions + 2 cross-country routes concurrently.
|
||||||
private nonisolated static func generateTripsInBackground(
|
private nonisolated static func generateTripsInBackground(
|
||||||
games: [Game],
|
games: [Game],
|
||||||
stadiums: [Stadium],
|
stadiums: [Stadium],
|
||||||
teams: [Team],
|
teams: [Team],
|
||||||
startDate: Date,
|
startDate: Date,
|
||||||
endDate: Date
|
endDate: Date
|
||||||
) -> [SuggestedTrip] {
|
) async -> [SuggestedTrip] {
|
||||||
// Build lookups (use reduce to handle potential duplicate UUIDs gracefully)
|
|
||||||
let stadiumsById = stadiums.reduce(into: [String: Stadium]()) { $0[$1.id] = $1 }
|
let stadiumsById = stadiums.reduce(into: [String: Stadium]()) { $0[$1.id] = $1 }
|
||||||
let teamsById = teams.reduce(into: [String: Team]()) { $0[$1.id] = $1 }
|
let teamsById = teams.reduce(into: [String: Team]()) { $0[$1.id] = $1 }
|
||||||
|
|
||||||
// Create a local planning engine for this background task
|
// Run all 3 regions + 2 cross-country routes concurrently
|
||||||
let planningEngine = TripPlanningEngine()
|
async let eastTrips = generateRegionTrips(
|
||||||
|
region: .east, games: games, stadiums: stadiums,
|
||||||
|
stadiumsById: stadiumsById, teamsById: teamsById,
|
||||||
|
startDate: startDate, endDate: endDate
|
||||||
|
)
|
||||||
|
async let centralTrips = generateRegionTrips(
|
||||||
|
region: .central, games: games, stadiums: stadiums,
|
||||||
|
stadiumsById: stadiumsById, teamsById: teamsById,
|
||||||
|
startDate: startDate, endDate: endDate
|
||||||
|
)
|
||||||
|
async let westTrips = generateRegionTrips(
|
||||||
|
region: .west, games: games, stadiums: stadiums,
|
||||||
|
stadiumsById: stadiumsById, teamsById: teamsById,
|
||||||
|
startDate: startDate, endDate: endDate
|
||||||
|
)
|
||||||
|
async let crossCountry1 = generateCrossCountryTrip(
|
||||||
|
games: games, stadiums: stadiumsById, teams: teamsById,
|
||||||
|
startDate: startDate, endDate: endDate, excludeGames: []
|
||||||
|
)
|
||||||
|
async let crossCountry2 = generateCrossCountryTrip(
|
||||||
|
games: games, stadiums: stadiumsById, teams: teamsById,
|
||||||
|
startDate: startDate, endDate: endDate,
|
||||||
|
excludeGames: [] // Can't depend on crossCountry1 without breaking parallelism
|
||||||
|
)
|
||||||
|
|
||||||
var generatedTrips: [SuggestedTrip] = []
|
var results: [SuggestedTrip] = []
|
||||||
|
results.append(contentsOf: await eastTrips)
|
||||||
|
results.append(contentsOf: await centralTrips)
|
||||||
|
results.append(contentsOf: await westTrips)
|
||||||
|
if let cc1 = await crossCountry1 { results.append(cc1) }
|
||||||
|
if let cc2 = await crossCountry2 { results.append(cc2) }
|
||||||
|
|
||||||
// Generate regional trips (East, Central, West)
|
return results
|
||||||
for region in [Region.east, Region.central, Region.west] {
|
|
||||||
let regionStadiumIds = Set(
|
|
||||||
stadiums
|
|
||||||
.filter { $0.region == region }
|
|
||||||
.map { $0.id }
|
|
||||||
)
|
|
||||||
|
|
||||||
let regionGames = games.filter { regionStadiumIds.contains($0.stadiumId) }
|
|
||||||
|
|
||||||
guard !regionGames.isEmpty else { continue }
|
|
||||||
|
|
||||||
// Single sport trip
|
|
||||||
if let singleSportTrip = generateRegionalTrip(
|
|
||||||
games: regionGames,
|
|
||||||
region: region,
|
|
||||||
singleSport: true,
|
|
||||||
stadiums: stadiumsById,
|
|
||||||
teams: teamsById,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate,
|
|
||||||
planningEngine: planningEngine
|
|
||||||
) {
|
|
||||||
generatedTrips.append(singleSportTrip)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Multi-sport trip
|
|
||||||
if let multiSportTrip = generateRegionalTrip(
|
|
||||||
games: regionGames,
|
|
||||||
region: region,
|
|
||||||
singleSport: false,
|
|
||||||
stadiums: stadiumsById,
|
|
||||||
teams: teamsById,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate,
|
|
||||||
planningEngine: planningEngine
|
|
||||||
) {
|
|
||||||
generatedTrips.append(multiSportTrip)
|
|
||||||
} else if let fallbackTrip = generateRegionalTrip(
|
|
||||||
// Fallback: if multi-sport fails, try another single-sport
|
|
||||||
games: regionGames,
|
|
||||||
region: region,
|
|
||||||
singleSport: true,
|
|
||||||
stadiums: stadiumsById,
|
|
||||||
teams: teamsById,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate,
|
|
||||||
excludingSport: generatedTrips.last?.sports.first,
|
|
||||||
planningEngine: planningEngine
|
|
||||||
) {
|
|
||||||
generatedTrips.append(fallbackTrip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cross-country trips (2)
|
|
||||||
for i in 0..<2 {
|
|
||||||
if let crossCountryTrip = generateCrossCountryTrip(
|
|
||||||
games: games,
|
|
||||||
stadiums: stadiumsById,
|
|
||||||
teams: teamsById,
|
|
||||||
startDate: startDate,
|
|
||||||
endDate: endDate,
|
|
||||||
excludeGames: i > 0 ? generatedTrips.last?.richGames.values.map { $0.game } ?? [] : []
|
|
||||||
) {
|
|
||||||
generatedTrips.append(crossCountryTrip)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return generatedTrips
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Trip Generation Helpers
|
// MARK: - Trip Generation Helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user