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:
Trey t
2026-02-09 17:44:25 -06:00
parent 1c57c47041
commit 68cb8927cf

View File

@@ -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