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
|
||||
let result = await Task.detached(priority: .userInitiated) {
|
||||
Self.generateTripsInBackground(
|
||||
await Self.generateTripsInBackground(
|
||||
games: games,
|
||||
stadiums: stadiums,
|
||||
teams: teams,
|
||||
@@ -145,92 +145,117 @@ final class SuggestedTripsGenerator {
|
||||
|
||||
// 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(
|
||||
games: [Game],
|
||||
stadiums: [Stadium],
|
||||
teams: [Team],
|
||||
startDate: Date,
|
||||
endDate: Date
|
||||
) -> [SuggestedTrip] {
|
||||
// Build lookups (use reduce to handle potential duplicate UUIDs gracefully)
|
||||
) async -> [SuggestedTrip] {
|
||||
let stadiumsById = stadiums.reduce(into: [String: Stadium]()) { $0[$1.id] = $1 }
|
||||
let teamsById = teams.reduce(into: [String: Team]()) { $0[$1.id] = $1 }
|
||||
|
||||
// Create a local planning engine for this background task
|
||||
let planningEngine = TripPlanningEngine()
|
||||
// Run all 3 regions + 2 cross-country routes concurrently
|
||||
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)
|
||||
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
|
||||
return results
|
||||
}
|
||||
|
||||
// MARK: - Trip Generation Helpers
|
||||
|
||||
Reference in New Issue
Block a user