From 81f4cce6f39c0150953763b4d11ed73b6fe42904 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Fri, 6 Mar 2026 00:17:18 -0600 Subject: [PATCH 01/33] initially adoption of `BitSet64` to improve performance - this commit replaces the `Set]? + locationTimeExclusivity: [BitSet64]? ) -> Set { var slots = Set(minimumCapacity: times * locations) if let exclusivities = locationTimeExclusivity { diff --git a/Sources/league-scheduling/Leagues.swift b/Sources/league-scheduling/Leagues.swift index 0903604..2b68464 100644 --- a/Sources/league-scheduling/Leagues.swift +++ b/Sources/league-scheduling/Leagues.swift @@ -80,12 +80,12 @@ typealias MaximumLocationAllocations = ContiguousArray`] -typealias PlaysAtTimes = ContiguousArray> +typealias PlaysAtTimes = ContiguousArray> /// Locations where an entry has already played at for the `day`. /// /// - Usage: [`LeagueEntry.IDValue`: `Set`] -typealias PlaysAtLocations = ContiguousArray> +typealias PlaysAtLocations = ContiguousArray> /// Slots where an entry has already played at for the `day`. /// diff --git a/Sources/league-scheduling/data/AssignSlots.swift b/Sources/league-scheduling/data/AssignSlots.swift index 93b7baa..95817bb 100644 --- a/Sources/league-scheduling/data/AssignSlots.swift +++ b/Sources/league-scheduling/data/AssignSlots.swift @@ -137,8 +137,8 @@ extension LeagueScheduleData { // TODO: pick the optimal combination that should be selected? combinationLoop: for combination in allowedDivisionCombinations { var assignedSlots = Set() - var combinationTimeAllocations:ContiguousArray> = .init( - repeating: Set(minimumCapacity: defaultMaxEntryMatchupsPerGameDay), + var combinationTimeAllocations:ContiguousArray> = .init( + repeating: BitSet64(), count: combination.first?.count ?? 10 ) for (divisionIndex, divisionCombination) in combination.enumerated() { @@ -159,7 +159,7 @@ extension LeagueScheduleData { #if LOG print("assignSlots;b2b;division=\(division);divisionCombination=\(divisionCombination);matchups.count=\(assignmentState.matchups.count);availableSlots=\(assignmentState.availableSlots.map({ $0.description }));remainingAllocations=\(assignmentState.remainingAllocations.map { $0.map({ $0.description }) })") #endif - var disallowedTimes = Set(minimumCapacity: defaultMaxEntryMatchupsPerGameDay) + var disallowedTimes = BitSet64() for (divisionCombinationIndex, amount) in divisionCombination.enumerated() { guard amount > 0 else { continue } let combinationTimeAllocation = combinationTimeAllocations[divisionCombinationIndex] diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index 9dd344b..bf9d44c 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -171,10 +171,10 @@ extension LeagueScheduleData { assignmentState.playsAt[unchecked: i].removeAll(keepingCapacity: true) } for i in 0..() + var adjacentTimes = BitSet64() var selectedEntries = Set(minimumCapacity: amount * entriesPerMatchup) // assign the first matchup, prioritizing the matchup's time @@ -296,8 +296,8 @@ extension LeagueScheduleData { static func adjacentTimes( for time: LeagueTimeIndex, entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay - ) -> Set { - var adjacentTimes = Set() + ) -> BitSet64 { + var adjacentTimes = BitSet64() let timeIndex = time % entryMatchupsPerGameDay if timeIndex == 0 { for i in 1.. Bool { var closest:LeagueTimeIndex? = nil - for playedTime in playsAtTimes { + playsAtTimes.forEachBit { playedTime in let distance = abs(playedTime.distance(to: time)) if closest == nil || distance < closest! { closest = LeagueTimeIndex(distance) diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift index 6ff923d..1c62525 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift @@ -7,8 +7,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: Set, - allowedLocations: Set, + allowedTimes: BitSet64, + allowedLocations: BitSet64, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, @@ -37,8 +37,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { static func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: Set, - allowedLocations: Set, + allowedTimes: BitSet64, + allowedLocations: BitSet64, playsAtTimes: PlaysAtTimes.Element, timeNumber: UInt8, locationNumber: UInt8, @@ -64,8 +64,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { static func isAllowed( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: Set, - allowedLocations: Set, + allowedTimes: BitSet64, + allowedLocations: BitSet64, playsAtTimes: PlaysAtTimes.Element, timeNumber: UInt8, locationNumber: UInt8, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift index d9d4d2e..1e4813c 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift @@ -6,8 +6,8 @@ protocol CanPlayAtProtocol: Sendable, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: Set, - allowedLocations: Set, + allowedTimes: BitSet64, + allowedLocations: BitSet64, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift index 5bae2d9..e2b697d 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift @@ -5,8 +5,8 @@ struct CanPlayAtSameLocationIfB2B: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: Set, - allowedLocations: Set, + allowedTimes: BitSet64, + allowedLocations: BitSet64, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift index f89f13f..f9f294f 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift @@ -9,8 +9,8 @@ struct CanPlayAtWithTravelDurations: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: Set, - allowedLocations: Set, + allowedTimes: BitSet64, + allowedLocations: BitSet64, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift index 69cc0ab..fa374c3 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift @@ -68,13 +68,13 @@ extension LeagueRequestPayload { fallbackDayOfWeek: startingDayOfWeek, teamsForDivision: teamsForDivision ) - let timesSet = Set(0..(0..(0..(0.. - var balancedLocations:Set + var balancedTimes:BitSet64 + var balancedLocations:BitSet64 if settings.balanceTimeStrictness != .lenient { balancedTimes = timesSet } else { - balancedTimes = [] + balancedTimes = .init() } if settings.balanceLocationStrictness != .lenient { balancedLocations = locationsSet } else { - balancedLocations = [] + balancedLocations = .init() } let correctMaximumPlayableMatchups = Self.calculateMaximumPlayableMatchups( @@ -143,8 +143,8 @@ extension LeagueRequestPayload { struct DivisionDefaults: Sendable, ~Copyable { let gameDays:[Set] let byes:[Set] - let gameTimes:[[Set]] - let gameLocations:[[Set]] + let gameTimes:[[BitSet64]] + let gameLocations:[[BitSet64]] } } @@ -155,8 +155,8 @@ extension LeagueRequestPayload { ) -> DivisionDefaults { var gameDays = [Set]() var byes = [Set]() - var gameTimes = [[Set]]() - var gameLocations = [[Set]]() + var gameTimes = [[BitSet64]]() + var gameLocations = [[BitSet64]]() gameDays.reserveCapacity(divisionsCount) byes.reserveCapacity(divisionsCount) gameTimes.reserveCapacity(divisionsCount) @@ -186,9 +186,9 @@ extension LeagueRequestPayload { byes.append([]) } if division.hasGameDayTimes { - gameTimes.append(division.gameDayTimes.times.map({ $0.set })) + gameTimes.append(division.gameDayTimes.times.map({ .init($0.times) })) } else { - var dgdt = [Set]() + var dgdt = [BitSet64]() for gameDay in 0..]() + var dgdl = [BitSet64]() for gameDay in 0..]() + var dgdt = [BitSet64]() for gameDay in 0..]() + var dgdl = [BitSet64]() for gameDay in 0.., defaultByes: Set, - defaultGameTimes: [Set], - defaultGameLocations: [Set] + defaultGameTimes: [BitSet64], + defaultGameLocations: [BitSet64] ) -> Runtime { return .init( id: id, @@ -33,15 +33,15 @@ extension LeagueEntry { /// Times this entry can play at for a specific day index. /// /// - Usage: [`LeagueDayIndex`: `Set`] - public let gameTimes:[Set] + public let gameTimes:[BitSet64] /// Locations this entry can play at for a specific day index. /// /// - Usage: [`LeagueDayIndex`: `Set`] - public let gameLocations:[Set] + public let gameLocations:[BitSet64] /// Home locations for this entry. - public let homeLocations:Set + public let homeLocations:BitSet64 /// Day indexes where this entry doesn't play due to being on a bye week. public let byes:Set @@ -54,15 +54,15 @@ extension LeagueEntry { protobuf: LeagueEntry, defaultGameDays: Set, defaultByes: Set, - defaultGameTimes: [Set], - defaultGameLocations: [Set] + defaultGameTimes: [BitSet64], + defaultGameLocations: [BitSet64] ) { self.id = id self.division = division gameDays = protobuf.hasGameDays ? Set(protobuf.gameDays.gameDayIndexes) : defaultGameDays - gameTimes = protobuf.hasGameDayTimes ? protobuf.gameDayTimes.times.map({ Set($0.times) }) : defaultGameTimes - gameLocations = protobuf.hasGameDayLocations ? protobuf.gameDayLocations.locations.map({ Set($0.locations) }) : defaultGameLocations - homeLocations = protobuf.hasHomeLocations ? Set(protobuf.homeLocations.homeLocations) : [] + gameTimes = protobuf.hasGameDayTimes ? protobuf.gameDayTimes.times.map({ .init($0.times) }) : defaultGameTimes + gameLocations = protobuf.hasGameDayLocations ? protobuf.gameDayLocations.locations.map({ .init($0.locations) }) : defaultGameLocations + homeLocations = protobuf.hasHomeLocations ? .init(protobuf.homeLocations.homeLocations) : .init() byes = protobuf.hasByes ? Set(protobuf.byes.byes) : defaultByes matchupsPerGameDay = protobuf.hasMatchupsPerGameDay ? protobuf.matchupsPerGameDay : nil } @@ -71,9 +71,9 @@ extension LeagueEntry { id: LeagueEntry.IDValue, division: LeagueDivision.IDValue, gameDays: Set, - gameTimes: [Set], - gameLocations: [Set], - homeLocations: Set, + gameTimes: [BitSet64], + gameLocations: [BitSet64], + homeLocations: BitSet64, byes: Set, matchupsPerGameDay: LitLeagues_Leagues_EntryMatchupsPerGameDay? ) { diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index 63ad833..265965e 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -16,12 +16,12 @@ extension LeagueGeneralSettings { public var defaultMaxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay public var maximumPlayableMatchups:[UInt32] public var matchupDuration:LeagueMatchupDuration - public var locationTimeExclusivities:[Set]? + public var locationTimeExclusivities:[BitSet64]? public var locationTravelDurations:[[LeagueMatchupDuration]]? public var balanceTimeStrictness:LeagueBalanceStrictness - public var balancedTimes:Set + public var balancedTimes:BitSet64 public var balanceLocationStrictness:LeagueBalanceStrictness - public var balancedLocations:Set + public var balancedLocations:BitSet64 public var redistributionSettings:LitLeagues_Leagues_RedistributionSettings? public var flags:UInt32 @@ -40,7 +40,7 @@ extension LeagueGeneralSettings { maximumPlayableMatchups = protobuf.maximumPlayableMatchups.array matchupDuration = protobuf.matchupDuration if protobuf.hasLocationTimeExclusivities { - locationTimeExclusivities = protobuf.locationTimeExclusivities.locations.map({ Set($0.times) }) + locationTimeExclusivities = protobuf.locationTimeExclusivities.locations.map({ .init($0.times) }) } else { locationTimeExclusivities = nil } @@ -50,9 +50,9 @@ extension LeagueGeneralSettings { locationTravelDurations = nil } balanceTimeStrictness = protobuf.balanceTimeStrictness - balancedTimes = Set(protobuf.balancedTimes.array) + balancedTimes = .init(protobuf.balancedTimes.array) balanceLocationStrictness = protobuf.balanceLocationStrictness - balancedLocations = Set(protobuf.balancedLocations.array) + balancedLocations = .init(protobuf.balancedLocations.array) if protobuf.hasRedistributionSettings { redistributionSettings = protobuf.redistributionSettings } else { @@ -100,12 +100,12 @@ extension LeagueGeneralSettings.Runtime { entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, maximumPlayableMatchups: [UInt32], matchupDuration: LeagueMatchupDuration, - locationTimeExclusivities: [Set]?, + locationTimeExclusivities: [BitSet64]?, locationTravelDurations: [[LeagueMatchupDuration]]?, balanceTimeStrictness: LeagueBalanceStrictness, - balancedTimes: Set, + balancedTimes: BitSet64, balanceLocationStrictness: LeagueBalanceStrictness, - balancedLocations: Set, + balancedLocations: BitSet64, redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, flags: UInt32 ) { diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift new file mode 100644 index 0000000..231cff8 --- /dev/null +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -0,0 +1,102 @@ + +/// - Warning: Only supports a maximum of 64 entries! +/// - Warning: Only supports integers < `64`! +public struct BitSet64: Hashable, Sendable { + private(set) var storage:UInt64 + + public init() { + storage = 0 + } + + public init(_ collection: some Collection) { + storage = 0 + for e in collection { + insert(e) + } + } + + var count: Int { + storage.nonzeroBitCount + } + var isEmpty: Bool { + count == 0 + } +} + +// MARK: contains +extension BitSet64 { + func contains(_ member: Element) -> Bool { + (storage & (1 << member)) != 0 + } +} + +// MARK: insert +extension BitSet64 { + mutating func insert(_ member: Element) { + storage |= (1 << member) + } +} + +// MARK: remove +extension BitSet64 { + mutating func remove(_ member: Element) { + storage &= ~(1 << member) + } +} + +// MARK: remove all +extension BitSet64 { + mutating func removeAll() { + storage = 0 + } +} + +// MARK: iterator +extension BitSet64 { + func forEachBit(_ yield: (Element) -> Void) { + var temp = storage + while temp != 0 { + let index = temp.trailingZeroBitCount + yield(Element(index)) + temp &= (temp - 1) + } + } + + func forEachBitWithReturn(_ yield: (Element) -> Result?) -> Result? { + var temp = storage + while temp != 0 { + let index = temp.trailingZeroBitCount + if let r = yield(Element(index)) { + return r + } + temp &= (temp - 1) + } + return nil + } +} + +// MARK: form union +extension BitSet64 { + mutating func formUnion(_ bitSet: Self) { + storage |= bitSet.storage + } +} + +// MARK: Random +extension BitSet64 { + func randomElement() -> Element? { + return nil // TODO: fix + } +} + +// MARK: Codable +extension BitSet64: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + storage = try container.decode(UInt64.self) + } + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(storage) + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/util/EntryAssignmentData.swift b/Sources/league-scheduling/util/EntryAssignmentData.swift index b77b953..8bcc7a8 100644 --- a/Sources/league-scheduling/util/EntryAssignmentData.swift +++ b/Sources/league-scheduling/util/EntryAssignmentData.swift @@ -33,8 +33,8 @@ struct EntryAssignmentData: Sendable { var maxSameOpponentMatchups:ContiguousArray var playsAt:Set - var playsAtTimes:Set - var playsAtLocations:Set + var playsAtTimes:BitSet64 + var playsAtLocations:BitSet64 var maxTimeAllocations:[LeagueTimeIndex] var maxLocationAllocations:[LeagueLocationIndex] @@ -84,7 +84,7 @@ extension EntryAssignmentData { extension EntryAssignmentData { mutating func resetPlaysAt() { playsAt.removeAll(keepingCapacity: true) - playsAtTimes.removeAll(keepingCapacity: true) - playsAtLocations.removeAll(keepingCapacity: true) + playsAtTimes.removeAll() + playsAtLocations.removeAll() } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/CanPlayAtTests.swift b/Tests/LeagueSchedulingTests/CanPlayAtTests.swift index ac27a68..f65aaee 100644 --- a/Tests/LeagueSchedulingTests/CanPlayAtTests.swift +++ b/Tests/LeagueSchedulingTests/CanPlayAtTests.swift @@ -12,7 +12,7 @@ struct CanPlayAtTests { var gameGap = GameGap.upTo(1).minMax var playsAt:PlaysAt.Element = [] - var playsAtTimes:PlaysAtTimes.Element = [] + var playsAtTimes:PlaysAtTimes.Element = .init() var timeNumbers:LeagueAssignedTimes.Element = .init(repeating: 0, count: times) var locationNumbers:LeagueAssignedLocations.Element = .init(repeating: 0, count: locations) let maxTimeNumbers:MaximumTimeAllocations.Element = .init(repeating: 1, count: times) @@ -23,8 +23,8 @@ struct CanPlayAtTests { #expect(CanPlayAtNormal.test( time: time, location: location, - allowedTimes: [0, 1, 2], - allowedLocations: [0, 1, 2], + allowedTimes: .init([0, 1, 2]), + allowedLocations: .init([0, 1, 2]), playsAtTimes: playsAtTimes, timeNumber: timeNumbers[unchecked: time], locationNumber: locationNumbers[unchecked: location], @@ -35,8 +35,8 @@ struct CanPlayAtTests { #expect(!CanPlayAtNormal.test( time: time, location: location, - allowedTimes: [], - allowedLocations: [], + allowedTimes: .init(), + allowedLocations: .init(), playsAtTimes: playsAtTimes, timeNumber: timeNumbers[unchecked: time], locationNumber: locationNumbers[unchecked: location], @@ -51,8 +51,8 @@ struct CanPlayAtTests { #expect(!CanPlayAtNormal.test( time: 0, location: location, - allowedTimes: [0, 1, 2], - allowedLocations: [0, 1, 2], + allowedTimes: .init([0, 1, 2]), + allowedLocations: .init([0, 1, 2]), playsAtTimes: playsAtTimes, timeNumber: timeNumbers[unchecked: 0], locationNumber: locationNumbers[unchecked: location], @@ -62,13 +62,13 @@ struct CanPlayAtTests { )) playsAt = [] - playsAtTimes = [] + playsAtTimes = .init() timeNumbers[0] = 1 #expect(!CanPlayAtNormal.test( time: 0, location: location, - allowedTimes: [0, 1, 2], - allowedLocations: [0, 1, 2], + allowedTimes: .init([0, 1, 2]), + allowedLocations: .init([0, 1, 2]), playsAtTimes: playsAtTimes, timeNumber: timeNumbers[0], locationNumber: locationNumbers[unchecked: location], diff --git a/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift b/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift index bcaa365..1d882c8 100644 --- a/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift +++ b/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift @@ -46,19 +46,19 @@ extension LeagueHTMLFormTests { var settings = try payload.parseSettings() for team in 0.., + balancedTimes: BitSet64, balanceTimeNumber: LeagueTimeIndex ) { for (entryID, assignedTimes) in assignedTimes.enumerated() { @@ -230,7 +230,7 @@ extension ScheduleExpectations { extension ScheduleExpectations { private func allocatedLessThanOrEqualToBalanceLocationNumber( assignedLocations: LeagueAssignedLocations, - balancedLocations: Set, + balancedLocations: BitSet64, balanceLocationNumber: LeagueLocationIndex ) { for (entryID, assignedLocations) in assignedLocations.enumerated() { diff --git a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift index c703ceb..adaefd1 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift @@ -14,12 +14,12 @@ extension ScheduleTestsProtocol { times: LeagueTimeIndex, locations: LeagueLocationIndex, teams: Int, - homeLocations: ContiguousArray> = [], + homeLocations: ContiguousArray> = [], byes: ContiguousArray> = [] ) -> [LeagueEntry.Runtime] { let playsOn = Array(repeating: Set(0.. = Set(0.. = Set(0.. = .init(0.. Date: Fri, 6 Mar 2026 03:21:51 -0600 Subject: [PATCH 02/33] bitset & canPlayAt changes; added `AbstractSet` and `BitSet128` - removed some code --- .../league-scheduling/data/AssignSlots.swift | 6 +- .../league-scheduling/data/MatchupBlock.swift | 8 +- Sources/league-scheduling/data/Shuffle.swift | 8 +- .../data/assignment/Assign.swift | 8 +- .../data/assignment/Unassign.swift | 8 +- .../data/canPlayAt/CanPlayAtNormal.swift | 12 +- .../data/canPlayAt/CanPlayAtProtocol.swift | 4 +- .../CanPlayAtSameLocationIfB2B.swift | 4 +- .../CanPlayAtWithTravelDurations.swift | 4 +- .../LeagueRequestPayload+ParseSettings.swift | 2 +- .../runtime/LeagueDaySettings+Runtime.swift | 6 +- .../runtime/LeagueEntry+Runtime.swift | 2 +- .../LeagueGeneralSettings+Runtime.swift | 4 - .../league-scheduling/util/AbstractSet.swift | 37 ++++++ .../league-scheduling/util/BitSet128.swift | 109 ++++++++++++++++++ Sources/league-scheduling/util/BitSet64.swift | 25 ++-- .../util/EntryAssignmentData.swift | 4 +- 17 files changed, 198 insertions(+), 53 deletions(-) create mode 100644 Sources/league-scheduling/util/AbstractSet.swift create mode 100644 Sources/league-scheduling/util/BitSet128.swift diff --git a/Sources/league-scheduling/data/AssignSlots.swift b/Sources/league-scheduling/data/AssignSlots.swift index 95817bb..60da107 100644 --- a/Sources/league-scheduling/data/AssignSlots.swift +++ b/Sources/league-scheduling/data/AssignSlots.swift @@ -138,7 +138,7 @@ extension LeagueScheduleData { combinationLoop: for combination in allowedDivisionCombinations { var assignedSlots = Set() var combinationTimeAllocations:ContiguousArray> = .init( - repeating: BitSet64(), + repeating: .init(), count: combination.first?.count ?? 10 ) for (divisionIndex, divisionCombination) in combination.enumerated() { @@ -189,8 +189,8 @@ extension LeagueScheduleData { continue combinationLoop } for matchup in matchups { - disallowedTimes.insert(matchup.time) - combinationTimeAllocations[divisionCombinationIndex].insert(matchup.time) + disallowedTimes.insertMember(matchup.time) + combinationTimeAllocations[divisionCombinationIndex].insertMember(matchup.time) assignedSlots.insert(matchup.slot) } assignmentState.availableSlots = slots.filter { !disallowedTimes.contains($0.time) } diff --git a/Sources/league-scheduling/data/MatchupBlock.swift b/Sources/league-scheduling/data/MatchupBlock.swift index 52a707a..3c7369f 100644 --- a/Sources/league-scheduling/data/MatchupBlock.swift +++ b/Sources/league-scheduling/data/MatchupBlock.swift @@ -189,7 +189,7 @@ extension LeagueScheduleData { canPlayAt: canPlayAt ) else { return nil } } - adjacentTimes.remove(time) + adjacentTimes.removeMember(time) #if LOG print("assignBlockOfMatchups;j=\(j);finished time \(time)") #endif @@ -301,15 +301,15 @@ extension LeagueScheduleData { let timeIndex = time % entryMatchupsPerGameDay if timeIndex == 0 { for i in 1.., - allowedLocations: BitSet64, + allowedTimes: some SetOfTimeIndexes, + allowedLocations: some SetOfLocationIndexes, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, @@ -37,8 +37,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { static func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: BitSet64, - allowedLocations: BitSet64, + allowedTimes: some SetOfTimeIndexes, + allowedLocations: some SetOfLocationIndexes, playsAtTimes: PlaysAtTimes.Element, timeNumber: UInt8, locationNumber: UInt8, @@ -64,8 +64,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { static func isAllowed( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: BitSet64, - allowedLocations: BitSet64, + allowedTimes: some SetOfTimeIndexes, + allowedLocations: some SetOfLocationIndexes, playsAtTimes: PlaysAtTimes.Element, timeNumber: UInt8, locationNumber: UInt8, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift index 1e4813c..a7300b9 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift @@ -6,8 +6,8 @@ protocol CanPlayAtProtocol: Sendable, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: BitSet64, - allowedLocations: BitSet64, + allowedTimes: some SetOfTimeIndexes, + allowedLocations: some SetOfLocationIndexes, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift index e2b697d..867cde5 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift @@ -5,8 +5,8 @@ struct CanPlayAtSameLocationIfB2B: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: BitSet64, - allowedLocations: BitSet64, + allowedTimes: some SetOfTimeIndexes, + allowedLocations: some SetOfLocationIndexes, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift index f9f294f..a9e933f 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift @@ -9,8 +9,8 @@ struct CanPlayAtWithTravelDurations: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: BitSet64, - allowedLocations: BitSet64, + allowedTimes: some SetOfTimeIndexes, + allowedLocations: some SetOfLocationIndexes, playsAt: PlaysAt.Element, playsAtTimes: PlaysAtTimes.Element, playsAtLocations: PlaysAtLocations.Element, diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift index fa374c3..3582feb 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift @@ -74,7 +74,7 @@ extension LeagueRequestPayload { if settings.hasLocationTimeExclusivities { for (location, exclusivities) in settings.locationTimeExclusivities.locations.enumerated() { if !exclusivities.times.isEmpty { - defaultTimeExclusivities[unchecked: location] = BitSet64(exclusivities.times) + defaultTimeExclusivities[unchecked: location] = .init(exclusivities.times) } } } diff --git a/Sources/league-scheduling/generated/runtime/LeagueDaySettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueDaySettings+Runtime.swift index f3bc118..3c0260f 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueDaySettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueDaySettings+Runtime.swift @@ -1,15 +1,11 @@ extension LeagueDaySettings { - public func runtime() throws(LeagueError) -> Runtime { - try .init(protobuf: self) - } - /// For optimal runtime performance. public struct Runtime: Codable, Sendable { public let general:LeagueGeneralSettings.Runtime public init(protobuf: LeagueDaySettings) throws(LeagueError) { - general = try protobuf.settings.runtime() + general = try .init(protobuf: protobuf.settings) } public init( diff --git a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift index bc488bc..3ba63fe 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift @@ -20,7 +20,7 @@ extension LeagueEntry { } /// For optimal runtime performance. - public struct Runtime: Codable, Hashable, Sendable { + public struct Runtime: Codable, Sendable { /// ID associated with this entry. public let id:LeagueEntry.IDValue diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index 265965e..8f8734c 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -2,10 +2,6 @@ import StaticDateTimes extension LeagueGeneralSettings { - public func runtime() throws(LeagueError) -> Runtime { - try .init(protobuf: self) - } - /// For optimal runtime performance public struct Runtime: Codable, Sendable { public var gameGap:GameGap diff --git a/Sources/league-scheduling/util/AbstractSet.swift b/Sources/league-scheduling/util/AbstractSet.swift new file mode 100644 index 0000000..42c0c35 --- /dev/null +++ b/Sources/league-scheduling/util/AbstractSet.swift @@ -0,0 +1,37 @@ + +public protocol AbstractSet: Sendable, ~Copyable { + associatedtype Element:Sendable + + init() + init(_ collection: some Collection) + + var count: Int { get } + var isEmpty: Bool { get } + + /// Returns a Boolean value that indicates whether the given element exists + /// in the set. + func contains(_ member: Element) -> Bool + + /// Inserts the given element in the set if it is not already present. + mutating func insertMember(_ member: Element) + + /// Removes the specified element from the set. + mutating func removeMember(_ member: Element) +} + +public protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex { +} +protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LeagueLocationIndex { +} + +extension Set: AbstractSet { + @inline(__always) + public mutating func removeMember(_ member: Element) { + self.remove(member) + } + + @inline(__always) + public mutating func insertMember(_ member: Element) { + insert(member) + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift new file mode 100644 index 0000000..bcc18d6 --- /dev/null +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -0,0 +1,109 @@ + +/// - Warning: Only supports a maximum of 128 entries! +/// - Warning: Only supports integers < `128`! +public struct BitSet128: Sendable { + private(set) var storage:UInt128 + + public init() { + storage = 0 + } + + public init(_ collection: some Collection) { + storage = 0 + for e in collection { + insertMember(e) + } + } + + public var count: Int { + storage.nonzeroBitCount + } + public var isEmpty: Bool { + count == 0 + } +} + +// MARK: contains +extension BitSet128 { + public func contains(_ member: Element) -> Bool { + (storage & (1 << member)) != 0 + } +} + +// MARK: insert +extension BitSet128 { + public mutating func insertMember(_ member: Element) { + storage |= (1 << member) + } +} + +// MARK: remove +extension BitSet128 { + public mutating func removeMember(_ member: Element) { + storage &= ~(1 << member) + } +} + +// MARK: remove all +extension BitSet128 { + mutating func removeAll() { + storage = 0 + } +} + +// MARK: iterator +extension BitSet128 { + func forEachBit(_ yield: (Element) -> Void) { + var temp = storage + while temp != 0 { + let index = temp.trailingZeroBitCount + yield(Element(index)) + temp &= (temp - 1) + } + } + + func forEachBitWithReturn(_ yield: (Element) -> Result?) -> Result? { + var temp = storage + while temp != 0 { + let index = temp.trailingZeroBitCount + if let r = yield(Element(index)) { + return r + } + temp &= (temp - 1) + } + return nil + } +} + +// MARK: form union +extension BitSet128 { + mutating func formUnion(_ bitSet: Self) { + storage |= bitSet.storage + } +} + +// MARK: Random +extension BitSet128 { + func randomElement() -> Element? { + let c = count + guard c > 0 else { return nil } + return Element.random(in: 0..: Hashable, Sendable { +public struct BitSet64: Sendable { private(set) var storage:UInt64 public init() { @@ -11,35 +11,35 @@ public struct BitSet64: Hashable, Sendabl public init(_ collection: some Collection) { storage = 0 for e in collection { - insert(e) + insertMember(e) } } - var count: Int { + public var count: Int { storage.nonzeroBitCount } - var isEmpty: Bool { + public var isEmpty: Bool { count == 0 } } // MARK: contains extension BitSet64 { - func contains(_ member: Element) -> Bool { + public func contains(_ member: Element) -> Bool { (storage & (1 << member)) != 0 } } // MARK: insert extension BitSet64 { - mutating func insert(_ member: Element) { + public mutating func insertMember(_ member: Element) { storage |= (1 << member) } } // MARK: remove extension BitSet64 { - mutating func remove(_ member: Element) { + public mutating func removeMember(_ member: Element) { storage &= ~(1 << member) } } @@ -85,7 +85,9 @@ extension BitSet64 { // MARK: Random extension BitSet64 { func randomElement() -> Element? { - return nil // TODO: fix + let c = count + guard c > 0 else { return nil } + return Element.random(in: 0.. Date: Fri, 6 Mar 2026 07:12:30 -0600 Subject: [PATCH 03/33] breaking changes - refactors to fully support bitsets - removed some code - moved some code to their own files - bitset fixes and performance improvements - removed `public` visibility from a lot of stuff since they aren't meant to be available (left over from legacy code) --- .../LeagueGenerationResult.swift | 16 +- .../league-scheduling/LeagueSchedule.swift | 99 ++----- Sources/league-scheduling/Leagues.swift | 20 +- .../data/LeagueScheduleData.swift | 19 +- .../data/LeagueScheduleDataSnapshot.swift | 2 +- .../league-scheduling/data/Redistribute.swift | 6 +- .../data/RedistributionData.swift | 4 +- .../AssignmentState.swift | 0 .../AssignmentStateProtocol.swift | 3 + .../LeagueRequestPayload+Extensions.swift | 8 - ...ft => LeagueRequestPayload+Generate.swift} | 249 ++++++++++------- .../runtime/LeagueDaySettings+Runtime.swift | 17 -- .../runtime/LeagueDivision+Runtime.swift | 16 +- .../runtime/LeagueEntry+Runtime.swift | 28 +- .../LeagueGeneralSettings+Runtime.swift | 252 ++++++++++-------- ...eagueGeneralSettings+RuntimeProtocol.swift | 124 +++++++++ .../LeagueRequestPayload+Runtime.swift | 39 ++- .../league-scheduling/util/AbstractSet.swift | 13 +- .../league-scheduling/util/BitSet128.swift | 32 ++- Sources/league-scheduling/util/BitSet64.swift | 32 ++- .../util/EntryAssignmentData.swift | 2 +- .../util/HomeAwayValue.swift | 12 + .../CanPlayAtTests.swift | 18 +- .../LeagueHTMLFormTests.swift | 5 +- .../MatchupBlockTests.swift | 17 ++ .../schedules/ScheduleBack2Back.swift | 14 +- .../schedules/ScheduleBaseball.swift | 8 +- .../schedules/ScheduleBeanBagToss.swift | 46 ++-- .../schedules/ScheduleFootball.swift | 4 +- .../schedules/ScheduleMisc.swift | 22 +- .../schedules/ScheduleRedistribution.swift | 16 +- .../schedules/ScheduleSameLocationIfB2B.swift | 18 +- .../schedules/ScheduleSoftball.swift | 12 +- .../schedules/ScheduleTball.swift | 4 +- .../schedules/ScheduleVolleyball.swift | 20 +- .../BalanceHomeAwayExpectations.swift | 13 + .../expectations/DayExpectations.swift | 5 +- .../expectations/ScheduleExpectations.swift | 19 +- .../util/ScheduleTestsProtocol.swift | 15 +- 39 files changed, 714 insertions(+), 535 deletions(-) rename Sources/league-scheduling/data/{ => assignmentState}/AssignmentState.swift (100%) create mode 100644 Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift rename Sources/league-scheduling/generated/extensions/{LeagueRequestPayload+ParseSettings.swift => LeagueRequestPayload+Generate.swift} (78%) delete mode 100644 Sources/league-scheduling/generated/runtime/LeagueDaySettings+Runtime.swift create mode 100644 Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift create mode 100644 Sources/league-scheduling/util/HomeAwayValue.swift diff --git a/Sources/league-scheduling/LeagueGenerationResult.swift b/Sources/league-scheduling/LeagueGenerationResult.swift index 54b54b9..34bf798 100644 --- a/Sources/league-scheduling/LeagueGenerationResult.swift +++ b/Sources/league-scheduling/LeagueGenerationResult.swift @@ -1,26 +1,14 @@ -#if canImport(FoundationEssentials) -import struct FoundationEssentials.UUID -#elseif canImport(Foundation) -import struct Foundation.UUID -#endif - public struct LeagueGenerationResult: Sendable { - public let id:UUID public let results:[LeagueGenerationData] public let error:String? - public let settings:LeagueRequestPayload.Runtime - public init( - id: UUID, + init( results: [LeagueGenerationData], - error: String?, - settings: LeagueRequestPayload.Runtime + error: String? ) { - self.id = id self.results = results self.error = error - self.settings = settings } } diff --git a/Sources/league-scheduling/LeagueSchedule.swift b/Sources/league-scheduling/LeagueSchedule.swift index fc339f9..d62c6a2 100644 --- a/Sources/league-scheduling/LeagueSchedule.swift +++ b/Sources/league-scheduling/LeagueSchedule.swift @@ -1,6 +1,4 @@ -import struct FoundationEssentials.UUID - #if canImport(SwiftGlibc) import SwiftGlibc #elseif canImport(Foundation) @@ -8,18 +6,8 @@ import Foundation #endif // TODO: support divisions on the same day with different times -public struct LeagueSchedule: Sendable, ~Copyable { - /// Settings for this schedule. - public let settings:LeagueRequestPayload.Runtime - - public init( - settings: LeagueRequestPayload.Runtime - ) { - self.settings = settings - } - - // MARK: Generate - public func generate() async -> LeagueGenerationResult { +enum LeagueSchedule: Sendable, ~Copyable { + static func generate(_ settings: some LeagueRequestPayload.RuntimeProtocol) async -> LeagueGenerationResult { var err:String? = nil var results = [LeagueGenerationData]() do { @@ -37,18 +25,16 @@ public struct LeagueSchedule: Sendable, ~Copyable { err = "\(error)" } return .init( - id: UUID(), results: results, - error: err, - settings: settings + error: err ) } } // MARK: Generate schedules extension LeagueSchedule { - func generateSchedules( - settings: LeagueRequestPayload.Runtime + static func generateSchedules( + settings: some LeagueRequestPayload.RuntimeProtocol ) async throws -> [LeagueGenerationData] { let divisionsCount = settings.divisions.count var divisionEntries:ContiguousArray> = .init(repeating: Set(), count: divisionsCount) @@ -62,11 +48,11 @@ extension LeagueSchedule { var maxStartingTimes:LeagueTimeIndex = 0 var maxLocations:LeagueLocationIndex = 0 for setting in settings.daySettings { - if setting.general.timeSlots > maxStartingTimes { - maxStartingTimes = LeagueTimeIndex(setting.general.timeSlots) + if setting.timeSlots > maxStartingTimes { + maxStartingTimes = LeagueTimeIndex(setting.timeSlots) } - if setting.general.locations > maxLocations { - maxLocations = setting.general.locations + if setting.locations > maxLocations { + maxLocations = setting.locations } } @@ -120,7 +106,7 @@ extension LeagueSchedule { // MARK: Timeout logic extension LeagueSchedule { - func withTimeout( + static func withTimeout( key: String, resultCount: Int, timeout: Duration, @@ -157,7 +143,7 @@ extension LeagueSchedule { extension LeagueSchedule { private static func generateSchedule( dayOfWeek: LeagueDayOfWeek, - settings: LeagueRequestPayload.Runtime, + settings: some LeagueRequestPayload.RuntimeProtocol, dataSnapshot: LeagueScheduleDataSnapshot, divisionsCount: Int, maxStartingTimes: LeagueTimeIndex, @@ -190,12 +176,8 @@ extension LeagueSchedule { while day < gameDays { if gameDaySettingValuesCount <= day { gameDaySettingValuesCount += 1 - let daySettings = settings.daySettings[unchecked: day].general - let availableSlots = LeagueSchedule.availableSlots( - times: daySettings.timeSlots, - locations: daySettings.locations, - locationTimeExclusivity: daySettings.locationTimeExclusivities - ) + let daySettings = settings.daySettings[unchecked: day] + let availableSlots = daySettings.availableSlots() do throws(LeagueError) { try data.newDay( day: day, @@ -292,7 +274,7 @@ extension LeagueSchedule { static func loadMaxAllocations( dataSnapshot: inout LeagueScheduleDataSnapshot, gameDayDivisionEntries: inout ContiguousArray>>, - settings: borrowing LeagueRequestPayload.Runtime, + settings: some LeagueRequestPayload.RuntimeProtocol, maxStartingTimes: LeagueTimeIndex, maxLocations: LeagueLocationIndex, scheduledEntries: Set @@ -306,7 +288,7 @@ extension LeagueSchedule { //var maxPossiblePlayedForLocations = [LeagueLocationIndex](repeating: 0, count: maxLocations) for day in 0.. LeagueTimeIndex { - var totalMatchupsPlayed:LeagueLocationIndex = 0 - var filledTimes:LeagueTimeIndex = 0 - while totalMatchupsPlayed < matchupsCount { - filledTimes += 1 - totalMatchupsPlayed += locations - } - #if LOG - print("LeagueSchedule;optimalTimeSlots;availableTimeSlots=\(availableTimeSlots);locations=\(locations);matchupsCount=\(matchupsCount);totalMatchupsPlayed=\(totalMatchupsPlayed);filledTimes=\(filledTimes)") - #endif - return min(availableTimeSlots, filledTimes) - } -} - -// MARK: Get available slots -extension LeagueSchedule { - static func availableSlots( - times: LeagueTimeIndex, - locations: LeagueLocationIndex, - locationTimeExclusivity: [BitSet64]? - ) -> Set { - var slots = Set(minimumCapacity: times * locations) - if let exclusivities = locationTimeExclusivity { - for location in 0..( diff --git a/Sources/league-scheduling/Leagues.swift b/Sources/league-scheduling/Leagues.swift index 2b68464..2265f4e 100644 --- a/Sources/league-scheduling/Leagues.swift +++ b/Sources/league-scheduling/Leagues.swift @@ -39,7 +39,7 @@ public typealias LeagueMatchupPair = LitLeagues_Leagues_MatchupPair /// Number of times an entry was assigned to play at home or away against another entry. /// /// - Usage: [`LeagueEntry.IDValue`: [opponent `LeagueEntry.IDValue`: [`home (0) or away (1)`: `total played`]]] -typealias AssignedEntryHomeAways = ContiguousArray> +typealias AssignedEntryHomeAways = ContiguousArray> /// Maximum number of times an entry can play against another entry. /// @@ -104,4 +104,22 @@ public struct Leagues3 { public static let maximumAllowedRegenerationAttemptsForANegativeDayIndex:LeagueRegenerationAttempt = 100 public static let maximumAllowedRegenerationAttemptsForASingleDay:LeagueRegenerationAttempt = 100 public static let failedRegenerationAttemptsThreshold:LeagueRegenerationAttempt = 10_000 +} + +// MARK: global +func optimalTimeSlots( + availableTimeSlots: LeagueTimeIndex, + locations: LeagueLocationIndex, + matchupsCount: LeagueLocationIndex +) -> LeagueTimeIndex { + var totalMatchupsPlayed:LeagueLocationIndex = 0 + var filledTimes:LeagueTimeIndex = 0 + while totalMatchupsPlayed < matchupsCount { + filledTimes += 1 + totalMatchupsPlayed += locations + } + #if LOG + print("LeagueSchedule;optimalTimeSlots;availableTimeSlots=\(availableTimeSlots);locations=\(locations);matchupsCount=\(matchupsCount);totalMatchupsPlayed=\(totalMatchupsPlayed);filledTimes=\(filledTimes)") + #endif + return min(availableTimeSlots, filledTimes) } \ No newline at end of file diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index bf9d44c..afc6385 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -82,21 +82,6 @@ extension LeagueScheduleData { } } -// MARK: HomeAwayValue -extension LeagueSchedule { - public struct HomeAwayValue: Codable, Sendable { - /// Number of matchups played at 'home'. - public var home:UInt8 - - /// Number of matchups played at 'away'. - public var away:UInt8 - - public var sum: UInt16 { - UInt16(home) + UInt16(away) - } - } -} - // MARK: New Day extension LeagueScheduleData { /// Indicates a new day will begin to be scheduled. @@ -107,10 +92,10 @@ extension LeagueScheduleData { /// - entryMatchupsPerGameDay: Number of times a single team will play on `day`. mutating func newDay( day: LeagueDayIndex, - daySettings: LeagueGeneralSettings.Runtime, + daySettings: some LeagueGeneralSettings.RuntimeProtocol, divisionEntries: ContiguousArray>, availableSlots: Set, - settings: LeagueRequestPayload.Runtime, + settings: some LeagueRequestPayload.RuntimeProtocol, generationData: inout LeagueGenerationData ) throws(LeagueError) { let now = clock.now diff --git a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift index ccd7b11..fc33abf 100644 --- a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift +++ b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift @@ -1,7 +1,7 @@ import StaticDateTimes -public struct LeagueScheduleDataSnapshot: Sendable { +struct LeagueScheduleDataSnapshot: Sendable { let entriesPerMatchup:LeagueEntriesPerMatchup let entriesCount:Int let entryDivisions:ContiguousArray diff --git a/Sources/league-scheduling/data/Redistribute.swift b/Sources/league-scheduling/data/Redistribute.swift index b5bca6d..7621a66 100644 --- a/Sources/league-scheduling/data/Redistribute.swift +++ b/Sources/league-scheduling/data/Redistribute.swift @@ -2,7 +2,7 @@ // MARK: Try redistributing extension LeagueScheduleData { mutating func tryRedistributing( - settings: LeagueRequestPayload.Runtime, + settings: some LeagueRequestPayload.RuntimeProtocol, generationData: inout LeagueGenerationData ) throws(LeagueError) { guard day > 0 else { @@ -38,7 +38,7 @@ extension LeagueScheduleData { mutating func tryRedistributing( startDayIndex: LeagueDayIndex, - settings: LeagueRequestPayload.Runtime, + settings: some LeagueRequestPayload.RuntimeProtocol, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable, generationData: inout LeagueGenerationData ) throws(LeagueError) { @@ -46,7 +46,7 @@ extension LeagueScheduleData { redistributionData = .init( dayIndex: day, startDayIndex: startDayIndex, - settings: settings, + settings: settings.daySettings[unchecked: day].redistributionSettings ?? settings.general.redistributionSettings, data: self ) } diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index c23360c..f26ef29 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -13,7 +13,7 @@ struct RedistributionData: Sendable { init( dayIndex: LeagueDayIndex, startDayIndex: LeagueDayIndex, - settings: LeagueRequestPayload.Runtime, + settings: LitLeagues_Leagues_RedistributionSettings?, data: borrowing LeagueScheduleData ) { self.startDayIndex = startDayIndex @@ -24,7 +24,7 @@ struct RedistributionData: Sendable { let threshold = (data.entriesCount / data.entriesPerMatchup)// * entryMatchupsPerGameDay var minMatchupsRequired = threshold var maxMovableMatchups = threshold - if let r = settings.daySettings[unchecked: dayIndex].general.redistributionSettings ?? settings.general.redistributionSettings { + if let r = settings { minMatchupsRequired = r.hasMinMatchupsRequired ? Int(r.minMatchupsRequired) : threshold maxMovableMatchups = r.hasMaxMovableMatchups ? Int(r.maxMovableMatchups) : threshold } diff --git a/Sources/league-scheduling/data/AssignmentState.swift b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift similarity index 100% rename from Sources/league-scheduling/data/AssignmentState.swift rename to Sources/league-scheduling/data/assignmentState/AssignmentState.swift diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift b/Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift new file mode 100644 index 0000000..39556e9 --- /dev/null +++ b/Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift @@ -0,0 +1,3 @@ + +protocol AssignmentStateProtocol: Sendable, ~Copyable { +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Extensions.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Extensions.swift index b3d451e..1a81771 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Extensions.swift @@ -69,12 +69,4 @@ extension LeagueRequestPayload { self.divisions = .init(divisions: divisions) self.entries = teams } -} - -// MARK: Generate -extension LeagueRequestPayload { - public func generate() async throws(LeagueError) -> LeagueGenerationResult { - let settings = try parseSettings() - return await LeagueSchedule.init(settings: settings).generate() - } } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift similarity index 78% rename from Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift rename to Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index 3582feb..a8d45b1 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+ParseSettings.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -1,5 +1,4 @@ -import struct FoundationEssentials.Date import StaticDateTimes #if canImport(SwiftGlibc) @@ -8,9 +7,9 @@ import SwiftGlibc import Foundation #endif -// MARK: Parse +// MARK: Generate extension LeagueRequestPayload { - func parseSettings() throws(LeagueError) -> LeagueRequestPayload.Runtime { + public func generate() async throws(LeagueError) -> LeagueGenerationResult { guard gameDays > 0 else { throw .malformedInput(msg: "'gameDays' needs to be > 0") } @@ -52,15 +51,96 @@ extension LeagueRequestPayload { } else { startingDayOfWeek = 0 } - var teamsForDivision = [Int](repeating: 0, count: divisionsCount) + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek + ) + } +} + +extension LeagueRequestPayload { + private func generate( + defaultGameGap: GameGap, + divisionsCount: Int, + startingDayOfWeek: LeagueDayOfWeek + ) async throws(LeagueError) -> LeagueGenerationResult { + switch settings.timeSlots { + case 1...64: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + t: BitSet64() + ) + case 1...128: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + t: BitSet128() + ) + default: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + t: Set() + ) + } + } + private func generate( + defaultGameGap: GameGap, + divisionsCount: Int, + startingDayOfWeek: LeagueDayOfWeek, + t: Times, + ) async throws(LeagueError) -> LeagueGenerationResult { + switch settings.locations { + case 1...64: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + defaultTimes: t, + defaultLocations: BitSet64() + ) + case 65...128: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + defaultTimes: t, + defaultLocations: BitSet128() + ) + default: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + defaultTimes: t, + defaultLocations: Set() + ) + } + } +} + +extension LeagueRequestPayload { + private func generate( + defaultGameGap: GameGap, + divisionsCount: Int, + startingDayOfWeek: LeagueDayOfWeek, + defaultTimes: Times, + defaultLocations: Locations + ) async throws(LeagueError) -> LeagueGenerationResult { let divisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) + var teamsForDivision = [Int](repeating: 0, count: divisionsCount) let entries = try parseEntries( divisionsCount: divisionsCount, teams: entries, teamsForDivision: &teamsForDivision, divisionDefaults: divisionDefaults ) - let runtimeDivisions = try parseDivisions( + let divisions = try parseDivisions( divisionsCount: divisionsCount, locations: settings.locations, divisionGameDays: divisionDefaults.gameDays, @@ -68,8 +148,14 @@ extension LeagueRequestPayload { fallbackDayOfWeek: startingDayOfWeek, teamsForDivision: teamsForDivision ) - let timesSet = BitSet64(0..(0.. - var balancedLocations:BitSet64 + var balancedTimes:Times + var balancedLocations:Locations if settings.balanceTimeStrictness != .lenient { balancedTimes = timesSet } else { - balancedTimes = .init() + balancedTimes = defaultTimes } if settings.balanceLocationStrictness != .lenient { - balancedLocations = locationsSet + balancedLocations = Locations(0..( + divisions: [LeagueDivision.Runtime], + entries: [LeagueEntry.Runtime], + correctMaximumPlayableMatchups: [UInt32], + general: T + ) async throws(LeagueError) -> LeagueGenerationResult { let daySettings = try parseDaySettings( general: general, correctMaximumPlayableMatchups: correctMaximumPlayableMatchups, entries: entries ) - return .init( + return await LeagueSchedule.generate(LeagueRequestPayload.Runtime( gameDays: gameDays, - divisions: runtimeDivisions, + divisions: divisions, entries: entries, general: general, daySettings: daySettings - ) + )) } } @@ -150,6 +244,7 @@ extension LeagueRequestPayload { // MARK: Load division defaults extension LeagueRequestPayload { + // TODO: fix (pass the generic times and locations) private func loadDivisionDefaults( divisionsCount: Int ) -> DivisionDefaults { @@ -354,85 +449,33 @@ extension LeagueRequestPayload { // MARK: Parse day settings extension LeagueRequestPayload { - private func parseDaySettings( - general: LeagueGeneralSettings.Runtime, + private func parseDaySettings( + general: T, correctMaximumPlayableMatchups: [UInt32], entries: [LeagueEntry.Runtime] - ) throws(LeagueError) -> [LeagueDaySettings.Runtime] { - var daySettings = [LeagueDaySettings.Runtime]() + ) throws(LeagueError) -> [T] { + var daySettings = [T]() daySettings.reserveCapacity(gameDays) if hasIndividualDaySettings { for dayIndex in 0.., defaultGameGap: GameGap, fallbackDayOfWeek: LeagueDayOfWeek, @@ -16,13 +16,13 @@ extension LeagueDivision { } /// For optimal runtime performance. - public struct Runtime: Codable, Sendable { - public let dayOfWeek:LeagueDayOfWeek - public let gameDays:Set - public let gameGaps:[GameGap] - public let maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchupsCap + struct Runtime: Sendable { + let dayOfWeek:LeagueDayOfWeek + let gameDays:Set + let gameGaps:[GameGap] + let maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchupsCap - public init( + init( protobuf: LeagueDivision, defaultGameDays: Set, defaultGameGap: GameGap, @@ -35,7 +35,7 @@ extension LeagueDivision { maxSameOpponentMatchups = protobuf.hasMaxSameOpponentMatchups ? protobuf.maxSameOpponentMatchups : fallbackMaxSameOpponentMatchups } - public init( + init( dayOfWeek: LeagueDayOfWeek, gameDays: Set, gameGaps: [GameGap], diff --git a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift index 3ba63fe..3269978 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift @@ -1,6 +1,6 @@ extension LeagueEntry { - public func runtime( + func runtime( id: IDValue, division: LeagueDivision.IDValue, defaultGameDays: Set, @@ -20,35 +20,35 @@ extension LeagueEntry { } /// For optimal runtime performance. - public struct Runtime: Codable, Sendable { + struct Runtime: Codable, Sendable { /// ID associated with this entry. - public let id:LeagueEntry.IDValue + let id:LeagueEntry.IDValue /// Division id this entry is in. - public let division:LeagueDivision.IDValue + let division:LeagueDivision.IDValue /// Game days this entry can play on. - public let gameDays:Set + let gameDays:Set /// Times this entry can play at for a specific day index. /// /// - Usage: [`LeagueDayIndex`: `Set`] - public let gameTimes:[BitSet64] + let gameTimes:[BitSet64] /// Locations this entry can play at for a specific day index. /// /// - Usage: [`LeagueDayIndex`: `Set`] - public let gameLocations:[BitSet64] + let gameLocations:[BitSet64] /// Home locations for this entry. - public let homeLocations:BitSet64 + let homeLocations:BitSet64 /// Day indexes where this entry doesn't play due to being on a bye week. - public let byes:Set + let byes:Set - public let matchupsPerGameDay:LitLeagues_Leagues_EntryMatchupsPerGameDay? + let matchupsPerGameDay:LitLeagues_Leagues_EntryMatchupsPerGameDay? - public init( + init( id: LeagueEntry.IDValue, division: LeagueDivision.IDValue, protobuf: LeagueEntry, @@ -67,7 +67,7 @@ extension LeagueEntry { matchupsPerGameDay = protobuf.hasMatchupsPerGameDay ? protobuf.matchupsPerGameDay : nil } - public init( + init( id: LeagueEntry.IDValue, division: LeagueDivision.IDValue, gameDays: Set, @@ -87,12 +87,12 @@ extension LeagueEntry { self.matchupsPerGameDay = matchupsPerGameDay } - public func hash(into hasher: inout Hasher) { + func hash(into hasher: inout Hasher) { hasher.combine(id) } /// - Returns: Maximum number of matchups this entry can play on the given day index. - public func maxMatchupsForGameDay( + func maxMatchupsForGameDay( day: LeagueDayIndex, fallback: LeagueEntryMatchupsPerGameDay ) -> LeagueEntryMatchupsPerGameDay { diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index 8f8734c..44216dd 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -2,92 +2,162 @@ import StaticDateTimes extension LeagueGeneralSettings { - /// For optimal runtime performance - public struct Runtime: Codable, Sendable { - public var gameGap:GameGap - public var timeSlots:LeagueTimeIndex - public var startingTimes:[StaticTime] - public var entriesPerLocation:LeagueEntriesPerMatchup - public var locations:LeagueLocationIndex - public var defaultMaxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay - public var maximumPlayableMatchups:[UInt32] - public var matchupDuration:LeagueMatchupDuration - public var locationTimeExclusivities:[BitSet64]? - public var locationTravelDurations:[[LeagueMatchupDuration]]? - public var balanceTimeStrictness:LeagueBalanceStrictness - public var balancedTimes:BitSet64 - public var balanceLocationStrictness:LeagueBalanceStrictness - public var balancedLocations:BitSet64 - public var redistributionSettings:LitLeagues_Leagues_RedistributionSettings? - public var flags:UInt32 - - public init( - protobuf: LeagueGeneralSettings - ) throws(LeagueError) { - guard let gameGap = GameGap(htmlInputValue: protobuf.gameGap) else { - throw .malformedInput(msg: "invalid GameGap htmlInputValue: \(protobuf.gameGap)") - } - self.gameGap = gameGap - timeSlots = protobuf.timeSlots - startingTimes = protobuf.startingTimes.times - entriesPerLocation = protobuf.entriesPerLocation - locations = protobuf.locations - defaultMaxEntryMatchupsPerGameDay = protobuf.entryMatchupsPerGameDay - maximumPlayableMatchups = protobuf.maximumPlayableMatchups.array - matchupDuration = protobuf.matchupDuration - if protobuf.hasLocationTimeExclusivities { - locationTimeExclusivities = protobuf.locationTimeExclusivities.locations.map({ .init($0.times) }) - } else { - locationTimeExclusivities = nil - } - if protobuf.hasLocationTravelDurations { - locationTravelDurations = protobuf.locationTravelDurations.locations.map({ $0.travelDurationTo }) - } else { - locationTravelDurations = nil - } - balanceTimeStrictness = protobuf.balanceTimeStrictness - balancedTimes = .init(protobuf.balancedTimes.array) - balanceLocationStrictness = protobuf.balanceLocationStrictness - balancedLocations = .init(protobuf.balancedLocations.array) - if protobuf.hasRedistributionSettings { - redistributionSettings = protobuf.redistributionSettings - } else { - redistributionSettings = nil - } - flags = protobuf.flags - } + struct Runtime< + Times: SetOfTimeIndexes, + Locations: SetOfLocationIndexes + >: RuntimeProtocol { + var gameGap:GameGap + var timeSlots:LeagueTimeIndex + var startingTimes:[StaticTime] + var entriesPerLocation:LeagueEntriesPerMatchup + var locations:LeagueLocationIndex + var defaultMaxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay + var maximumPlayableMatchups:[UInt32] + var matchupDuration:LeagueMatchupDuration + var locationTimeExclusivities:[Times]? + var locationTravelDurations:[[LeagueMatchupDuration]]? + var balanceTimeStrictness:LeagueBalanceStrictness + var balancedTimes:Times + var balanceLocationStrictness:LeagueBalanceStrictness + var balancedLocations:Locations + var redistributionSettings:LitLeagues_Leagues_RedistributionSettings? + var flags:UInt32 } } -// MARK: Flags extension LeagueGeneralSettings.Runtime { - func isFlag(_ flag: LeagueSettingFlags) -> Bool { - flags & UInt32(1 << flag.rawValue) != 0 - } - - public var optimizeTimes: Bool { - isFlag(.optimizeTimes) + init( + gameGap: GameGap, + protobuf: LeagueGeneralSettings + ) { + self.gameGap = gameGap + timeSlots = protobuf.timeSlots + startingTimes = protobuf.startingTimes.times + entriesPerLocation = protobuf.entriesPerLocation + locations = protobuf.locations + defaultMaxEntryMatchupsPerGameDay = protobuf.entryMatchupsPerGameDay + maximumPlayableMatchups = protobuf.maximumPlayableMatchups.array + matchupDuration = protobuf.matchupDuration + if protobuf.hasLocationTimeExclusivities { + locationTimeExclusivities = protobuf.locationTimeExclusivities.locations.map({ .init($0.times) }) + } else { + locationTimeExclusivities = nil + } + if protobuf.hasLocationTravelDurations { + locationTravelDurations = protobuf.locationTravelDurations.locations.map({ $0.travelDurationTo }) + } else { + locationTravelDurations = nil + } + balanceTimeStrictness = protobuf.balanceTimeStrictness + balancedTimes = .init(protobuf.balancedTimes.array) + balanceLocationStrictness = protobuf.balanceLocationStrictness + balancedLocations = .init(protobuf.balancedLocations.array) + if protobuf.hasRedistributionSettings { + redistributionSettings = protobuf.redistributionSettings + } else { + redistributionSettings = nil + } + flags = protobuf.flags } - public var prioritizeEarlierTimes: Bool { - isFlag(.prioritizeEarlierTimes) + mutating func apply( + gameDays: LeagueDayIndex, + entriesCount: Int, + correctMaximumPlayableMatchups: [UInt32], + general: Self, + customDaySettings: LeagueGeneralSettings + ) { + if customDaySettings.hasGameGap, let gg = GameGap(htmlInputValue: customDaySettings.gameGap) { + self.gameGap = gg + } + if customDaySettings.hasTimeSlots { + self.timeSlots = customDaySettings.timeSlots + } + if customDaySettings.hasStartingTimes { + self.startingTimes = customDaySettings.startingTimes.times + } + if customDaySettings.hasEntriesPerLocation { + self.entriesPerLocation = customDaySettings.entriesPerLocation + } + if customDaySettings.hasLocations { + self.locations = customDaySettings.locations + } + if customDaySettings.hasEntryMatchupsPerGameDay { + self.defaultMaxEntryMatchupsPerGameDay = customDaySettings.entryMatchupsPerGameDay + } + if customDaySettings.hasMaximumPlayableMatchups { + self.maximumPlayableMatchups = LeagueRequestPayload.calculateMaximumPlayableMatchups( + gameDays: gameDays, + entryMatchupsPerGameDay: self.defaultMaxEntryMatchupsPerGameDay, + teamsCount: entriesCount, + maximumPlayableMatchups: customDaySettings.maximumPlayableMatchups.array + ) + } else { + self.maximumPlayableMatchups = correctMaximumPlayableMatchups + } + if customDaySettings.hasMatchupDuration { + self.matchupDuration = customDaySettings.matchupDuration + } + if customDaySettings.hasLocationTimeExclusivities { + self.locationTimeExclusivities = customDaySettings.locationTimeExclusivities.locations.map({ Times($0.times) }) + } + if customDaySettings.hasLocationTravelDurations { + self.locationTravelDurations = customDaySettings.locationTravelDurations.locations.map({ $0.travelDurationTo }) + } + if customDaySettings.hasBalanceTimeStrictness { + self.balanceTimeStrictness = customDaySettings.balanceTimeStrictness + } + if customDaySettings.hasBalanceLocationStrictness { + self.balanceLocationStrictness = customDaySettings.balanceLocationStrictness + } + if customDaySettings.hasRedistributionSettings { + self.redistributionSettings = customDaySettings.redistributionSettings + if let defaultSettings = general.redistributionSettings { + if !customDaySettings.redistributionSettings.hasMinMatchupsRequired, defaultSettings.hasMinMatchupsRequired { + self.redistributionSettings!.minMatchupsRequired = defaultSettings.minMatchupsRequired + } + if !customDaySettings.redistributionSettings.hasMaxMovableMatchups, defaultSettings.hasMaxMovableMatchups { + self.redistributionSettings!.maxMovableMatchups = defaultSettings.maxMovableMatchups + } + } + } + if customDaySettings.hasFlags { + self.flags = customDaySettings.flags + } } - public var prioritizeHomeAway: Bool { - isFlag(.prioritizeHomeAway) + func availableSlots() -> Set { + var slots = Set(minimumCapacity: timeSlots * locations) + if let exclusivities = locationTimeExclusivities { + for location in 0.. Bool { + balancedTimes.contains(timeSlot) } - - public var sameLocationIfB2B: Bool { - isFlag(.sameLocationIfBackToBack) + func containsBalancedLocation(_ location: LeagueLocationIndex) -> Bool { + balancedLocations.contains(location) } -} -extension LeagueGeneralSettings.Runtime { - public init( + init( gameGap: GameGap, timeSlots: LeagueTimeIndex, startingTimes: [StaticTime], @@ -96,12 +166,12 @@ extension LeagueGeneralSettings.Runtime { entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, maximumPlayableMatchups: [UInt32], matchupDuration: LeagueMatchupDuration, - locationTimeExclusivities: [BitSet64]?, + locationTimeExclusivities: [Times]?, locationTravelDurations: [[LeagueMatchupDuration]]?, balanceTimeStrictness: LeagueBalanceStrictness, - balancedTimes: BitSet64, + balancedTimes: Times, balanceLocationStrictness: LeagueBalanceStrictness, - balancedLocations: BitSet64, + balancedLocations: Locations, redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, flags: UInt32 ) { @@ -122,34 +192,4 @@ extension LeagueGeneralSettings.Runtime { self.redistributionSettings = redistributionSettings self.flags = flags } -} - -// MARK: Compute settings -extension LeagueGeneralSettings.Runtime { - /// Modifies `timeSlots` and `startingTimes` taking into account current settings. - public mutating func computeSettings( - day: LeagueDayIndex, - entries: [LeagueEntry.Runtime] - ) { - if optimizeTimes { - var maxMatchupsPlayedToday:LeagueLocationIndex = 0 - for entry in entries { - if entry.gameDays.contains(day) && !entry.byes.contains(day) { - maxMatchupsPlayedToday += entry.maxMatchupsForGameDay(day: day, fallback: defaultMaxEntryMatchupsPerGameDay) - } - } - maxMatchupsPlayedToday /= entriesPerLocation - let filledTimeSlots = LeagueSchedule.optimalTimeSlots( - availableTimeSlots: timeSlots, - locations: locations, - matchupsCount: maxMatchupsPlayedToday - ) - while filledTimeSlots < timeSlots { - timeSlots -= 1 - } - while filledTimeSlots < startingTimes.count { - startingTimes.removeLast() - } - } - } } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift new file mode 100644 index 0000000..e37dec2 --- /dev/null +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift @@ -0,0 +1,124 @@ + +import StaticDateTimes + +extension LeagueGeneralSettings { + protocol RuntimeProtocol: Sendable, ~Copyable { + associatedtype Times:SetOfTimeIndexes + associatedtype Locations:SetOfLocationIndexes + + var gameGap: GameGap { get set } + var timeSlots: LeagueTimeIndex { get set } + + var startingTimes: [StaticTime] { get set } + var entriesPerLocation: LeagueEntriesPerMatchup { get set } + var locations: LeagueLocationIndex { get set } + var defaultMaxEntryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay { get set } + var maximumPlayableMatchups: [UInt32] { get set } + var matchupDuration: LeagueMatchupDuration { get set } + var locationTimeExclusivities: [Times]? { get set } + var locationTravelDurations: [[LeagueMatchupDuration]]? { get set } + var balanceTimeStrictness: LeagueBalanceStrictness { get set } + var balancedTimes: Times { get set } + var balanceLocationStrictness: LeagueBalanceStrictness { get set } + var balancedLocations: Locations { get set } + var redistributionSettings: LitLeagues_Leagues_RedistributionSettings? { get set } + var flags: UInt32 { get set } + + init(protobuf: LeagueGeneralSettings) throws(LeagueError) + init(gameGap: GameGap, protobuf: LeagueGeneralSettings) + init( + gameGap: GameGap, + timeSlots: LeagueTimeIndex, + startingTimes: [StaticTime], + entriesPerLocation: LeagueEntriesPerMatchup, + locations: LeagueLocationIndex, + entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, + maximumPlayableMatchups: [UInt32], + matchupDuration: LeagueMatchupDuration, + locationTimeExclusivities: [Times]?, + locationTravelDurations: [[LeagueMatchupDuration]]?, + balanceTimeStrictness: LeagueBalanceStrictness, + balancedTimes: Times, + balanceLocationStrictness: LeagueBalanceStrictness, + balancedLocations: Locations, + redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, + flags: UInt32 + ) + + mutating func apply( + gameDays: LeagueDayIndex, + entriesCount: Int, + correctMaximumPlayableMatchups: [UInt32], + general: borrowing Self, + customDaySettings: LeagueGeneralSettings + ) + + func availableSlots() -> Set + } +} + +// MARK: Flags +extension LeagueGeneralSettings.RuntimeProtocol { + func isFlag(_ flag: LeagueSettingFlags) -> Bool { + flags & UInt32(1 << flag.rawValue) != 0 + } + + var optimizeTimes: Bool { + isFlag(.optimizeTimes) + } + + var prioritizeEarlierTimes: Bool { + isFlag(.prioritizeEarlierTimes) + } + + var prioritizeHomeAway: Bool { + isFlag(.prioritizeHomeAway) + } + + var balanceHomeAway: Bool { + isFlag(.balanceHomeAway) + } + + var sameLocationIfB2B: Bool { + isFlag(.sameLocationIfBackToBack) + } +} + +// MARK: Compute settings +extension LeagueGeneralSettings.RuntimeProtocol { + init( + protobuf: LeagueGeneralSettings + ) throws(LeagueError) { + guard let gameGap = GameGap(htmlInputValue: protobuf.gameGap) else { + throw .malformedInput(msg: "invalid GameGap htmlInputValue: \(protobuf.gameGap)") + } + self.init(gameGap: gameGap, protobuf: protobuf) + } + + /// Modifies `timeSlots` and `startingTimes` taking into account current settings. + mutating func computeSettings( + day: LeagueDayIndex, + entries: [LeagueEntry.Runtime] + ) { + if optimizeTimes { + var maxMatchupsPlayedToday:LeagueLocationIndex = 0 + for entry in entries { + if entry.gameDays.contains(day) && !entry.byes.contains(day) { + maxMatchupsPlayedToday += entry.maxMatchupsForGameDay(day: day, fallback: defaultMaxEntryMatchupsPerGameDay) + } + } + maxMatchupsPlayedToday /= entriesPerLocation + let filledTimeSlots = optimalTimeSlots( + availableTimeSlots: timeSlots, + locations: locations, + matchupsCount: maxMatchupsPlayedToday + ) + while filledTimeSlots < timeSlots { + timeSlots -= 1 + } + while filledTimeSlots < startingTimes.count { + startingTimes.removeLast() + } + } + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift index 276aa7a..d03e73d 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift @@ -9,31 +9,52 @@ import SwiftProtobuf // MARK: Runtime extension LeagueRequestPayload { + protocol RuntimeProtocol: Sendable, ~Copyable { + associatedtype ConcreteGeneralSettings:LeagueGeneralSettings.RuntimeProtocol + + /// Number of days where games are played. + var gameDays: LeagueDayIndex { get } + + /// Divisions associated with this schedule. + var divisions: [LeagueDivision.Runtime] { get } + + /// Entries that participate in this schedule. + var entries: [LeagueEntry.Runtime] { get } + + /// General settings for this schedule. + var general: ConcreteGeneralSettings { get } + + /// Individual settings for the given day index. + /// + /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] + var daySettings: [ConcreteGeneralSettings] { get } + } + /// For optimal runtime performance. - public struct Runtime: Codable, Sendable { + struct Runtime: RuntimeProtocol { /// Number of days where games are played. - public let gameDays:LeagueDayIndex + let gameDays:LeagueDayIndex /// Divisions associated with this schedule. - public let divisions:[LeagueDivision.Runtime] + let divisions:[LeagueDivision.Runtime] /// Entries that participate in this schedule. - public let entries:[LeagueEntry.Runtime] + let entries:[LeagueEntry.Runtime] /// General settings for this schedule. - public let general:LeagueGeneralSettings.Runtime + let general:T /// Individual settings for the given day index. /// /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] - public let daySettings:[LeagueDaySettings.Runtime] + let daySettings:[T] - public init( + init( gameDays: LeagueDayIndex, divisions: [LeagueDivision.Runtime], entries: [LeagueEntry.Runtime], - general: LeagueGeneralSettings.Runtime, - daySettings: [LeagueDaySettings.Runtime] + general: T, + daySettings: [T] ) { self.gameDays = gameDays self.divisions = divisions diff --git a/Sources/league-scheduling/util/AbstractSet.swift b/Sources/league-scheduling/util/AbstractSet.swift index 42c0c35..178d1ae 100644 --- a/Sources/league-scheduling/util/AbstractSet.swift +++ b/Sources/league-scheduling/util/AbstractSet.swift @@ -1,5 +1,5 @@ -public protocol AbstractSet: Sendable, ~Copyable { +protocol AbstractSet: Sendable, ~Copyable { associatedtype Element:Sendable init() @@ -19,19 +19,22 @@ public protocol AbstractSet: Sendable, ~Copyable { mutating func removeMember(_ member: Element) } -public protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex { +protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex { } protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LeagueLocationIndex { } extension Set: AbstractSet { @inline(__always) - public mutating func removeMember(_ member: Element) { + mutating func removeMember(_ member: Element) { self.remove(member) } @inline(__always) - public mutating func insertMember(_ member: Element) { + mutating func insertMember(_ member: Element) { insert(member) } -} \ No newline at end of file +} + +extension Set: SetOfTimeIndexes {} +extension Set: SetOfLocationIndexes {} \ No newline at end of file diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index bcc18d6..02efc08 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -1,45 +1,45 @@ /// - Warning: Only supports a maximum of 128 entries! /// - Warning: Only supports integers < `128`! -public struct BitSet128: Sendable { +struct BitSet128: Sendable { private(set) var storage:UInt128 - public init() { + init() { storage = 0 } - public init(_ collection: some Collection) { + init(_ collection: some Collection) { storage = 0 for e in collection { insertMember(e) } } - public var count: Int { + var count: Int { storage.nonzeroBitCount } - public var isEmpty: Bool { - count == 0 + var isEmpty: Bool { + storage == 0 } } // MARK: contains extension BitSet128 { - public func contains(_ member: Element) -> Bool { + func contains(_ member: Element) -> Bool { (storage & (1 << member)) != 0 } } // MARK: insert extension BitSet128 { - public mutating func insertMember(_ member: Element) { + mutating func insertMember(_ member: Element) { storage |= (1 << member) } } // MARK: remove extension BitSet128 { - public mutating func removeMember(_ member: Element) { + mutating func removeMember(_ member: Element) { storage &= ~(1 << member) } } @@ -85,19 +85,23 @@ extension BitSet128 { // MARK: Random extension BitSet128 { func randomElement() -> Element? { - let c = count - guard c > 0 else { return nil } - return Element.random(in: 0..: Sendable { +struct BitSet64: Sendable { private(set) var storage:UInt64 - public init() { + init() { storage = 0 } - public init(_ collection: some Collection) { + init(_ collection: some Collection) { storage = 0 for e in collection { insertMember(e) } } - public var count: Int { + var count: Int { storage.nonzeroBitCount } - public var isEmpty: Bool { - count == 0 + var isEmpty: Bool { + storage == 0 } } // MARK: contains extension BitSet64 { - public func contains(_ member: Element) -> Bool { + func contains(_ member: Element) -> Bool { (storage & (1 << member)) != 0 } } // MARK: insert extension BitSet64 { - public mutating func insertMember(_ member: Element) { + mutating func insertMember(_ member: Element) { storage |= (1 << member) } } // MARK: remove extension BitSet64 { - public mutating func removeMember(_ member: Element) { + mutating func removeMember(_ member: Element) { storage &= ~(1 << member) } } @@ -85,19 +85,23 @@ extension BitSet64 { // MARK: Random extension BitSet64 { func randomElement() -> Element? { - let c = count - guard c > 0 else { return nil } - return Element.random(in: 0.. Bool { + lhs.storage == rhs.storage + } + public func hash(into hasher: inout Hasher) { + hasher.combine(storage) + } +} +extension BitSet128: Hashable { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.storage == rhs.storage + } + public func hash(into hasher: inout Hasher) { + hasher.combine(storage) + } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift index be564c7..919c745 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift @@ -44,8 +44,8 @@ struct ScheduleBack2Back: ScheduleTestsProtocol { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 264, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 264, data: data) } } @@ -54,10 +54,10 @@ extension ScheduleBack2Back { @Test(.timeLimit(.minutes(1))) func scheduleB2B_11GameDays4Times6Locations2Divisions24Teams14_10() async throws { let schedule = try Self.scheduleB2B_11GameDays4Times6Locations2Divisions24Teams14_10() - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 264, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 264, data: data) } - static func scheduleB2B_11GameDays4Times6Locations2Divisions24Teams14_10() throws -> LeagueSchedule { + static func scheduleB2B_11GameDays4Times6Locations2Divisions24Teams14_10() throws -> some LeagueRequestPayload.RuntimeProtocol { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 6, 24) var entryDivisions = [LeagueDivision.IDValue]() @@ -135,7 +135,7 @@ extension ScheduleBack2Back { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 200, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 200, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift index 2fa5733..86aa789 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift @@ -40,8 +40,8 @@ extension ScheduleBaseball { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 28, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 28, data: data) } } @@ -78,7 +78,7 @@ extension ScheduleBaseball { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 32, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 32, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift index 1b853e2..4d2e938 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift @@ -9,14 +9,14 @@ struct ScheduleBeanBagToss: ScheduleTestsProtocol { @Test(.timeLimit(.minutes(1))) func schedule8GameDays3Times3Locations1Division9Teams() async throws { let schedule = try Self.schedule8GameDays3Times3Locations1Division9Teams() - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 72, data: data ) } - static func schedule8GameDays3Times3Locations1Division9Teams() throws -> LeagueSchedule { + static func schedule8GameDays3Times3Locations1Division9Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 3, 3, 9) let entries = getEntries( @@ -86,8 +86,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 264, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 264, data: data) } } @@ -124,8 +124,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 65, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 65, data: data) } } @@ -167,8 +167,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 125, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 125, data: data) } } @@ -208,8 +208,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 240, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 240, data: data) } } @@ -250,8 +250,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 253, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 253, data: data) } } @@ -292,8 +292,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 230, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 230, data: data) } } @@ -336,8 +336,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 260, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 260, data: data) } } @@ -379,8 +379,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 253, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 253, data: data) } } @@ -419,8 +419,8 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 210, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 210, data: data) } } @@ -460,7 +460,7 @@ extension ScheduleBeanBagToss { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 230, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 230, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift index dd4701a..260ba30 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift @@ -41,7 +41,7 @@ extension ScheduleFootball { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 30, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 30, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift index 322cbc2..b3a8df0 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift @@ -38,9 +38,9 @@ struct ScheduleMisc: ScheduleTestsProtocol { teams: teams ) ) - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 20, data: data ) @@ -86,9 +86,9 @@ extension ScheduleMisc { teams: teams ) ) - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 40, data: data ) @@ -128,9 +128,9 @@ extension ScheduleMisc { teams: teams ) ) - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 18, data: data ) @@ -176,9 +176,9 @@ extension ScheduleMisc { teams: teams ) ) - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 144, data: data ) @@ -190,15 +190,15 @@ extension ScheduleMisc { @Test(.timeLimit(.minutes(1))) func schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() async throws { let schedule = try Self.schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 200, data: data ) } - static func schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() throws -> LeagueSchedule { + static func schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() throws -> some LeagueRequestPayload.RuntimeProtocol { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 5, 20) var entryDivisions = [LeagueDivision.IDValue]() diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift index f843fdd..3dff988 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift @@ -48,8 +48,8 @@ extension ScheduleRedistribution { divisions: divisions, entries: entries ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 30, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 30, data: data) } } @@ -92,8 +92,8 @@ extension ScheduleRedistribution { divisions: divisions, entries: entries ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 33, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 33, data: data) } } @@ -138,8 +138,8 @@ extension ScheduleRedistribution { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 75, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 75, data: data) } } @@ -183,7 +183,7 @@ extension ScheduleRedistribution { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 60, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 60, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift index 6c64b24..8e50287 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift @@ -9,14 +9,14 @@ struct ScheduleSameLocationIfB2B: ScheduleTestsProtocol { @Test func scheduleSameLocationIfB2B_8GameDays3Times3Locations1Division9Teams() async throws { let schedule = try Self.scheduleSameLocationIfB2B_8GameDays3Times3Locations1Division9Teams() - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 72, data: data ) } - static func scheduleSameLocationIfB2B_8GameDays3Times3Locations1Division9Teams() throws -> LeagueSchedule { + static func scheduleSameLocationIfB2B_8GameDays3Times3Locations1Division9Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 3, 3, 9) let entries = getEntries( @@ -54,14 +54,14 @@ extension ScheduleSameLocationIfB2B { @Test func scheduleSameLocationIfB2B_12GameDays3Times1Locations1Division5Teams() async throws { let schedule = try Self.scheduleSameLocationIfB2B_12GameDays3Times1Locations1Division5Teams() - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 30, data: data ) } - static func scheduleSameLocationIfB2B_12GameDays3Times1Locations1Division5Teams() throws -> LeagueSchedule { + static func scheduleSameLocationIfB2B_12GameDays3Times1Locations1Division5Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 3, 1, 5) let entries = getEntries( @@ -100,14 +100,14 @@ extension ScheduleSameLocationIfB2B { @Test func scheduleSameLocationIfB2B_10GameDays4Times4Locations1Division16Teams() async throws { let schedule = try Self.scheduleSameLocationIfB2B_10GameDays4Times4Locations1Division16Teams() - let data = await schedule.generate() + let data = await LeagueSchedule.generate(schedule) try expectations( - settings: schedule.settings, + settings: schedule, matchupsCount: 160, data: data ) } - static func scheduleSameLocationIfB2B_10GameDays4Times4Locations1Division16Teams() throws -> LeagueSchedule { + static func scheduleSameLocationIfB2B_10GameDays4Times4Locations1Division16Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 4, 16) let entries = getEntries( diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift index 2d020f6..0a420c1 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift @@ -44,8 +44,8 @@ extension ScheduleSoftball { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 132, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 132, data: data) } } @@ -84,8 +84,8 @@ extension ScheduleSoftball { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 160, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 160, data: data) } } @@ -123,7 +123,7 @@ extension ScheduleSoftball { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 90, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 90, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift index 7add9ac..7d8fdef 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift @@ -42,7 +42,7 @@ extension ScheduleTball { teams: teams ) ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 12, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 12, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift index 4e0c982..f2f8517 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift @@ -40,8 +40,8 @@ struct ScheduleVolleyball: ScheduleTestsProtocol { divisions: divisions, entries: entries ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 36, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 36, data: data) } } @@ -81,8 +81,8 @@ extension ScheduleVolleyball { divisions: divisions, entries: entries ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 24, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 24, data: data) } } @@ -122,8 +122,8 @@ extension ScheduleVolleyball { divisions: divisions, entries: entries ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 36, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 36, data: data) } } @@ -164,8 +164,8 @@ extension ScheduleVolleyball { divisions: divisions, entries: entries ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 48, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 48, data: data) } } @@ -205,7 +205,7 @@ extension ScheduleVolleyball { divisions: divisions, entries: entries ) - let data = await schedule.generate() - try expectations(settings: schedule.settings, matchupsCount: 72, data: data) + let data = await LeagueSchedule.generate(schedule) + try expectations(settings: schedule, matchupsCount: 72, data: data) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift index 30356c6..5b50f2f 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift @@ -72,4 +72,17 @@ extension BalanceHomeAwayExpectations { } return (home, away) } +} + +extension LeagueEntry.Runtime: Hashable { + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id + && lhs.division == rhs.division + && lhs.gameDays == rhs.gameDays + && lhs.gameTimes == rhs.gameTimes + && lhs.gameLocations == rhs.gameLocations + && lhs.homeLocations == rhs.homeLocations + && lhs.byes == rhs.byes + && lhs.matchupsPerGameDay == rhs.matchupsPerGameDay + } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift index b03b2d6..2efa5e2 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift @@ -1,12 +1,11 @@ -import LeagueScheduling +@testable import LeagueScheduling import Testing struct DayExpectations: ScheduleTestsProtocol { - let settings:LeagueGeneralSettings.Runtime let b2bMatchupsAtDifferentLocations:Set - func expectations() { + func expectations(_ settings: some LeagueGeneralSettings.RuntimeProtocol) { if settings.sameLocationIfB2B { sameLocationIfB2B() } diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift index 29eb7d4..8069cd7 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift @@ -8,7 +8,7 @@ protocol ScheduleExpectations: Sendable { // MARK: Expectations extension ScheduleExpectations { func expectations( - settings: LeagueRequestPayload.Runtime, + settings: some LeagueRequestPayload.RuntimeProtocol, matchupsCount: Int, data: LeagueGenerationResult ) throws { @@ -32,11 +32,11 @@ extension ScheduleExpectations { var maxStartingTimes:LeagueTimeIndex = 0 var maxLocations:LeagueLocationIndex = 0 for setting in settings.daySettings { - if setting.general.startingTimes.count > maxStartingTimes { - maxStartingTimes = LeagueTimeIndex(setting.general.startingTimes.count) + if setting.startingTimes.count > maxStartingTimes { + maxStartingTimes = LeagueTimeIndex(setting.startingTimes.count) } - if setting.general.locations > maxLocations { - maxLocations = setting.general.locations + if setting.locations > maxLocations { + maxLocations = setting.locations } } @@ -91,12 +91,11 @@ extension ScheduleExpectations { ) } } - let settings = data.settings.daySettings[dayIndex].general + let settings = settings.daySettings[dayIndex] let dayExpectations = DayExpectations( - settings: settings, b2bMatchupsAtDifferentLocations: b2bMatchupsAtDifferentLocations ) - dayExpectations.expectations() + dayExpectations.expectations(settings) if true { printMatchups(day: dayIndex, matchups) @@ -214,7 +213,7 @@ extension ScheduleExpectations { extension ScheduleExpectations { private func allocatedLessThanOrEqualToBalanceTimeNumber( assignedTimes: LeagueAssignedTimes, - balancedTimes: BitSet64, + balancedTimes: some SetOfTimeIndexes, balanceTimeNumber: LeagueTimeIndex ) { for (entryID, assignedTimes) in assignedTimes.enumerated() { @@ -230,7 +229,7 @@ extension ScheduleExpectations { extension ScheduleExpectations { private func allocatedLessThanOrEqualToBalanceLocationNumber( assignedLocations: LeagueAssignedLocations, - balancedLocations: BitSet64, + balancedLocations: some SetOfLocationIndexes, balanceLocationNumber: LeagueLocationIndex ) { for (entryID, assignedLocations) in assignedLocations.enumerated() { diff --git a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift index adaefd1..ca2029f 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift @@ -86,7 +86,7 @@ extension ScheduleTestsProtocol { divisionsCanPlayOnSameDay: Bool = true, divisionsCanPlayAtSameTime: Bool = true, entries: [LeagueEntry.Runtime] - ) -> LeagueSchedule { + ) -> some LeagueRequestPayload.RuntimeProtocol { let correctMaximumPlayableMatchups = LeagueRequestPayload.calculateMaximumPlayableMatchups( gameDays: gameDays, entryMatchupsPerGameDay: entryMatchupsPerGameDay, @@ -95,7 +95,7 @@ extension ScheduleTestsProtocol { ) let times:LeagueTimeIndex = LeagueTimeIndex(startingTimes.count) let timeSlots:Set = Set(0.. = .init(0.. = Set(0.., Set>]() daySettings.reserveCapacity(gameDays) for day in 0.. Date: Fri, 6 Mar 2026 21:15:29 -0600 Subject: [PATCH 04/33] nonsense --- .../data/AssignMatchup.swift | 6 +++-- .../AssignmentStateProtocol.swift | 3 --- .../data/canPlayAt/CanPlayAt+GameGap.swift | 4 ++-- .../data/canPlayAt/CanPlayAtNormal.swift | 20 ++++++++-------- .../data/canPlayAt/CanPlayAtProtocol.swift | 8 +++---- .../CanPlayAtSameLocationIfB2B.swift | 12 +++++----- .../CanPlayAtWithTravelDurations.swift | 8 +++---- .../data/selectSlot/SelectSlotB2B.swift | 18 +++++++------- .../selectSlot/SelectSlotEarliestTime.swift | 6 +++-- ...SlotEarliestTimeAndSameLocationIfB2B.swift | 18 +++++++------- .../data/selectSlot/SelectSlotNormal.swift | 6 +++-- .../data/selectSlot/SelectSlotProtocol.swift | 6 +++-- .../LeagueRequestPayload+Generate.swift | 24 ++++++++++--------- .../runtime/LeagueEntry+Runtime.swift | 2 +- .../league-scheduling/util/AbstractSet.swift | 8 +++++++ .../league-scheduling/util/BitSet128.swift | 16 ++----------- Sources/league-scheduling/util/BitSet64.swift | 22 +++++++---------- .../CanPlayAtTests.swift | 2 +- .../LeagueSchedulingTests/League3Tests.swift | 2 +- .../BalanceHomeAwayExpectations.swift | 22 ++++++++--------- .../DivisionEntryExpectations.swift | 4 ++-- .../expectations/ScheduleExpectations.swift | 6 ++--- 22 files changed, 109 insertions(+), 114 deletions(-) delete mode 100644 Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift diff --git a/Sources/league-scheduling/data/AssignMatchup.swift b/Sources/league-scheduling/data/AssignMatchup.swift index cfd5d27..3efe482 100644 --- a/Sources/league-scheduling/data/AssignMatchup.swift +++ b/Sources/league-scheduling/data/AssignMatchup.swift @@ -48,8 +48,10 @@ extension AssignmentState { team2: pair.team2, assignedTimes: assignedTimes, assignedLocations: assignedLocations, - playsAtTimes: playsAtTimes, - playsAtLocations: playsAtLocations, + team1PlaysAtTimes: playsAtTimes[unchecked: pair.team1], + team1PlaysAtLocations: playsAtLocations[unchecked: pair.team1], + team2PlaysAtTimes: playsAtTimes[unchecked: pair.team2], + team2PlaysAtLocations: playsAtLocations[unchecked: pair.team2], playableSlots: &slots ) #if LOG diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift b/Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift deleted file mode 100644 index 39556e9..0000000 --- a/Sources/league-scheduling/data/assignmentState/AssignmentStateProtocol.swift +++ /dev/null @@ -1,3 +0,0 @@ - -protocol AssignmentStateProtocol: Sendable, ~Copyable { -} \ No newline at end of file diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift index 21117ed..5f4719c 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift @@ -5,11 +5,11 @@ struct CanPlayAtGameGap: Sendable, ~Copyable { /// - Returns: If a team with the provided `playsAtTimes` can play at the given `time` taking into account a `gameGap`. static func test( time: LeagueTimeIndex, - playsAtTimes: PlaysAtTimes.Element, + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, gameGap: GameGap.TupleValue ) -> Bool { var closest:LeagueTimeIndex? = nil - playsAtTimes.forEachBit { playedTime in + playsAtTimes.forEach { playedTime in let distance = abs(playedTime.distance(to: time)) if closest == nil || distance < closest! { closest = LeagueTimeIndex(distance) diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift index bfe3d02..47cc51a 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift @@ -7,11 +7,11 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: some SetOfTimeIndexes, - allowedLocations: some SetOfLocationIndexes, + allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, + allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, - playsAtTimes: PlaysAtTimes.Element, - playsAtLocations: PlaysAtLocations.Element, + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + playsAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, timeNumber: UInt8, locationNumber: UInt8, maxTimeNumber: UInt8, @@ -37,9 +37,9 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { static func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: some SetOfTimeIndexes, - allowedLocations: some SetOfLocationIndexes, - playsAtTimes: PlaysAtTimes.Element, + allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, + allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, timeNumber: UInt8, locationNumber: UInt8, maxTimeNumber: UInt8, @@ -64,9 +64,9 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { static func isAllowed( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: some SetOfTimeIndexes, - allowedLocations: some SetOfLocationIndexes, - playsAtTimes: PlaysAtTimes.Element, + allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, + allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, timeNumber: UInt8, locationNumber: UInt8, maxTimeNumber: UInt8, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift index a7300b9..37c5fc2 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift @@ -6,11 +6,11 @@ protocol CanPlayAtProtocol: Sendable, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: some SetOfTimeIndexes, - allowedLocations: some SetOfLocationIndexes, + allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, + allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, - playsAtTimes: PlaysAtTimes.Element, - playsAtLocations: PlaysAtLocations.Element, + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + playsAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, timeNumber: UInt8, locationNumber: UInt8, maxTimeNumber: UInt8, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift index 867cde5..1b72593 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift @@ -5,11 +5,11 @@ struct CanPlayAtSameLocationIfB2B: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: some SetOfTimeIndexes, - allowedLocations: some SetOfLocationIndexes, + allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, + allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, - playsAtTimes: PlaysAtTimes.Element, - playsAtLocations: PlaysAtLocations.Element, + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + playsAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, timeNumber: UInt8, locationNumber: UInt8, maxTimeNumber: UInt8, @@ -40,8 +40,8 @@ struct CanPlayAtSameLocationIfB2B: CanPlayAtProtocol, ~Copyable { static func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - playsAtTimes: PlaysAtTimes.Element, - playsAtLocations: PlaysAtLocations.Element + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + playsAtLocations: borrowing some SetOfLocationIndexes & ~Copyable ) -> Bool { if time > 0 && playsAtTimes.contains(time-1) || playsAtTimes.contains(time+1) { // is back-to-back diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift index a9e933f..6215e6b 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift @@ -9,11 +9,11 @@ struct CanPlayAtWithTravelDurations: CanPlayAtProtocol, ~Copyable { func test( time: LeagueTimeIndex, location: LeagueLocationIndex, - allowedTimes: some SetOfTimeIndexes, - allowedLocations: some SetOfLocationIndexes, + allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, + allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, - playsAtTimes: PlaysAtTimes.Element, - playsAtLocations: PlaysAtLocations.Element, + playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + playsAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, timeNumber: UInt8, locationNumber: UInt8, maxTimeNumber: UInt8, diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift index ff48f76..3ed0480 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift @@ -7,14 +7,15 @@ struct SelectSlotB2B: SelectSlotProtocol, ~Copyable { team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - playsAtTimes: PlaysAtTimes, - playsAtLocations: PlaysAtLocations, + team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, playableSlots: inout Set ) -> LeagueAvailableSlot? { filter( - team1: team1, - team2: team2, - playsAtTimes: playsAtTimes, + team1PlaysAtTimes: team1PlaysAtTimes, + team2PlaysAtTimes: team2PlaysAtTimes, playableSlots: &playableSlots ) return SelectSlotNormal.select( @@ -30,13 +31,12 @@ struct SelectSlotB2B: SelectSlotProtocol, ~Copyable { extension SelectSlotB2B { /// Mutates `playableSlots`, if `team1` AND `team2` haven't played already, so it only contains the first slots applicable for a matchup block. private func filter( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - playsAtTimes: PlaysAtTimes, + team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, playableSlots: inout Set ) { //print("filterSlotBack2Back;playsAtTimes[unchecked: team1].isEmpty=\(playsAtTimes[unchecked: team1].isEmpty);playsAtTimes[unchecked: team2].isEmpty=\(playsAtTimes[unchecked: team2].isEmpty)") - if playsAtTimes[unchecked: team1].isEmpty && playsAtTimes[unchecked: team2].isEmpty { + if team1PlaysAtTimes.isEmpty && team2PlaysAtTimes.isEmpty { playableSlots = playableSlots.filter({ $0.time % entryMatchupsPerGameDay == 0 }) } } diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift index 53c0c99..4b96d8d 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift @@ -5,8 +5,10 @@ struct SelectSlotEarliestTime: SelectSlotProtocol, ~Copyable { team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - playsAtTimes: PlaysAtTimes, - playsAtLocations: PlaysAtLocations, + team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, playableSlots: inout Set ) -> LeagueAvailableSlot? { return Self.select( diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift index bef0976..240ab70 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift @@ -5,14 +5,14 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - playsAtTimes: PlaysAtTimes, - playsAtLocations: PlaysAtLocations, + team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, playableSlots: inout Set ) -> LeagueAvailableSlot? { guard !playableSlots.isEmpty else { return nil } - let homePlaysAtTimes = playsAtTimes[unchecked: team1] - let awayPlaysAtTimes = playsAtTimes[unchecked: team2] - guard !(homePlaysAtTimes.isEmpty || awayPlaysAtTimes.isEmpty) else { + guard !(team1PlaysAtTimes.isEmpty || team2PlaysAtTimes.isEmpty) else { return SelectSlotEarliestTime.select( team1: team1, team2: team2, @@ -24,10 +24,8 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable // at least one of the teams already plays let team1Times = assignedTimes[unchecked: team1] let team1Locations = assignedLocations[unchecked: team1] - let team1PlaysAtLocations = playsAtLocations[unchecked: team1] let team2Times = assignedTimes[unchecked: team2] let team2Locations = assignedLocations[unchecked: team2] - let team2PlaysAtLocations = playsAtLocations[unchecked: team2] var nonBackToBackSlots = [LeagueAvailableSlot]() nonBackToBackSlots.reserveCapacity(playableSlots.count) @@ -40,9 +38,9 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable team2Locations: team2Locations, playableSlots: playableSlots ) { - if targetSlot.time > 0 && (homePlaysAtTimes.contains(targetSlot.time-1) || awayPlaysAtTimes.contains(targetSlot.time-1)) - || homePlaysAtTimes.contains(targetSlot.time+1) - || awayPlaysAtTimes.contains(targetSlot.time+1) { + if targetSlot.time > 0 && (team1PlaysAtTimes.contains(targetSlot.time-1) || team2PlaysAtTimes.contains(targetSlot.time-1)) + || team1PlaysAtTimes.contains(targetSlot.time+1) + || team2PlaysAtTimes.contains(targetSlot.time+1) { // is back-to-back if team1PlaysAtLocations.contains(targetSlot.location) || team2PlaysAtLocations.contains(targetSlot.location) { // make them play b2b on the same location diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift index 22bd181..9efeb72 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift @@ -5,8 +5,10 @@ struct SelectSlotNormal: SelectSlotProtocol, ~Copyable { team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - playsAtTimes: PlaysAtTimes, - playsAtLocations: PlaysAtLocations, + team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, playableSlots: inout Set ) -> LeagueAvailableSlot? { return Self.select( diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift index bc9efd1..b1fc24e 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift @@ -5,8 +5,10 @@ protocol SelectSlotProtocol: Sendable, ~Copyable { team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - playsAtTimes: PlaysAtTimes, - playsAtLocations: PlaysAtLocations, + team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, playableSlots: inout Set ) -> LeagueAvailableSlot? } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index a8d45b1..7415064 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -132,7 +132,7 @@ extension LeagueRequestPayload { defaultTimes: Times, defaultLocations: Locations ) async throws(LeagueError) -> LeagueGenerationResult { - let divisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) + let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) var teamsForDivision = [Int](repeating: 0, count: divisionsCount) let entries = try parseEntries( divisionsCount: divisionsCount, @@ -234,10 +234,10 @@ extension LeagueRequestPayload { // MARK: Division defaults extension LeagueRequestPayload { - struct DivisionDefaults: Sendable, ~Copyable { + struct DivisionDefaults: Sendable, ~Copyable { let gameDays:[Set] let byes:[Set] - let gameTimes:[[BitSet64]] + let gameTimes:[[Times]] let gameLocations:[[BitSet64]] } } @@ -245,12 +245,12 @@ extension LeagueRequestPayload { // MARK: Load division defaults extension LeagueRequestPayload { // TODO: fix (pass the generic times and locations) - private func loadDivisionDefaults( + private func loadDivisionDefaults( divisionsCount: Int - ) -> DivisionDefaults { + ) -> DivisionDefaults { var gameDays = [Set]() var byes = [Set]() - var gameTimes = [[BitSet64]]() + var gameTimes = [[Times]]() var gameLocations = [[BitSet64]]() gameDays.reserveCapacity(divisionsCount) byes.reserveCapacity(divisionsCount) @@ -283,7 +283,7 @@ extension LeagueRequestPayload { if division.hasGameDayTimes { gameTimes.append(division.gameDayTimes.times.map({ .init($0.times) })) } else { - var dgdt = [BitSet64]() + var dgdt = [Times]() for gameDay in 0..]() + var dgdt = [Times]() for gameDay in 0..( divisionsCount: Int, teams: [LeagueEntry], teamsForDivision: inout [Int], - divisionDefaults: borrowing DivisionDefaults + divisionDefaults: borrowing DivisionDefaults ) throws(LeagueError) -> [LeagueEntry.Runtime] { var entries = [LeagueEntry.Runtime]() entries.reserveCapacity(teams.count) @@ -373,13 +373,15 @@ extension LeagueRequestPayload { } } let division = min(team.division, UInt32(divisionsCount - 1)) + // TODO: fix + let defaultGameTimes = divisionDefaults.gameTimes[unchecked: division].map { BitSet64.init($0) } teamsForDivision[Int(division)] += 1 entries.append(team.runtime( id: LeagueEntry.IDValue(i), division: division, defaultGameDays: divisionDefaults.gameDays[unchecked: division], defaultByes: divisionDefaults.byes[unchecked: division], - defaultGameTimes: divisionDefaults.gameTimes[unchecked: division], + defaultGameTimes: defaultGameTimes, defaultGameLocations: divisionDefaults.gameLocations[unchecked: division] )) } diff --git a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift index 3269978..97cd8df 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift @@ -20,7 +20,7 @@ extension LeagueEntry { } /// For optimal runtime performance. - struct Runtime: Codable, Sendable { + struct Runtime: Sendable { /// ID associated with this entry. let id:LeagueEntry.IDValue diff --git a/Sources/league-scheduling/util/AbstractSet.swift b/Sources/league-scheduling/util/AbstractSet.swift index 178d1ae..9189d45 100644 --- a/Sources/league-scheduling/util/AbstractSet.swift +++ b/Sources/league-scheduling/util/AbstractSet.swift @@ -17,6 +17,10 @@ protocol AbstractSet: Sendable, ~Copyable { /// Removes the specified element from the set. mutating func removeMember(_ member: Element) + + mutating func removeAll() + + func forEach(_ body: (Element) throws -> Void) rethrows } protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex { @@ -30,6 +34,10 @@ extension Set: AbstractSet { self.remove(member) } + mutating func removeAll() { + self.removeAll(keepingCapacity: true) + } + @inline(__always) mutating func insertMember(_ member: Element) { insert(member) diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index 02efc08..3f4754b 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -53,11 +53,11 @@ extension BitSet128 { // MARK: iterator extension BitSet128 { - func forEachBit(_ yield: (Element) -> Void) { + func forEach(_ body: (Element) throws -> Void) rethrows { var temp = storage while temp != 0 { let index = temp.trailingZeroBitCount - yield(Element(index)) + try body(Element(index)) temp &= (temp - 1) } } @@ -95,18 +95,6 @@ extension BitSet128 { } } -// MARK: Codable -extension BitSet128: Codable { - init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - storage = try container.decode(UInt128.self) - } - func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(storage) - } -} - // MARK: AbstractSet extension BitSet128: AbstractSet {} extension BitSet128: SetOfTimeIndexes where Element == LeagueTimeIndex {} diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index 541e7f1..a2bce14 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -14,6 +14,12 @@ struct BitSet64: Sendable { insertMember(e) } } + init(_ set: T) where T.Element == Element { + storage = 0 + set.forEach { + insertMember($0) + } + } var count: Int { storage.nonzeroBitCount @@ -53,11 +59,11 @@ extension BitSet64 { // MARK: iterator extension BitSet64 { - func forEachBit(_ yield: (Element) -> Void) { + func forEach(_ body: (Element) throws -> Void) rethrows { var temp = storage while temp != 0 { let index = temp.trailingZeroBitCount - yield(Element(index)) + try body(Element(index)) temp &= (temp - 1) } } @@ -95,18 +101,6 @@ extension BitSet64 { } } -// MARK: Codable -extension BitSet64: Codable { - init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - storage = try container.decode(UInt64.self) - } - func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(storage) - } -} - // MARK: AbstractSet extension BitSet64: AbstractSet {} extension BitSet64: SetOfTimeIndexes where Element == LeagueTimeIndex {} diff --git a/Tests/LeagueSchedulingTests/CanPlayAtTests.swift b/Tests/LeagueSchedulingTests/CanPlayAtTests.swift index 6d5dec4..6520654 100644 --- a/Tests/LeagueSchedulingTests/CanPlayAtTests.swift +++ b/Tests/LeagueSchedulingTests/CanPlayAtTests.swift @@ -12,7 +12,7 @@ struct CanPlayAtTests { var gameGap = GameGap.upTo(1).minMax var playsAt:PlaysAt.Element = [] - var playsAtTimes:PlaysAtTimes.Element = .init() + var playsAtTimes:BitSet64 = .init() var timeNumbers:LeagueAssignedTimes.Element = .init(repeating: 0, count: times) var locationNumbers:LeagueAssignedLocations.Element = .init(repeating: 0, count: locations) let maxTimeNumbers:MaximumTimeAllocations.Element = .init(repeating: 1, count: times) diff --git a/Tests/LeagueSchedulingTests/League3Tests.swift b/Tests/LeagueSchedulingTests/League3Tests.swift index 1cef242..918ef8c 100644 --- a/Tests/LeagueSchedulingTests/League3Tests.swift +++ b/Tests/LeagueSchedulingTests/League3Tests.swift @@ -9,7 +9,7 @@ struct League3Tests: ScheduleExpectations { let schedule = try ScheduleBeanBagToss.schedule8GameDays3Times3Locations1Division9Teams() //let schedule = try ScheduleMisc.schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() while !Task.isCancelled { - let result = await schedule.generate() + let result = await LeagueSchedule.generate(schedule) throughput += 1 if let e = result.error, !e.contains("(timed out;") { failed[e, default: 0] += 1 diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift index 5b50f2f..121f8fa 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift @@ -26,7 +26,7 @@ struct BalanceHomeAwayExpectations: ScheduleTestsProtocol { } } for opponentEntry in divisionEntries { - if entry != opponentEntry { + if !entry.isEqual(to: opponentEntry) { let value = assignedEntryHomeAways[unchecked: entry.id][unchecked: opponentEntry.id] let sum = value.sum #expect(sum <= cap) @@ -74,15 +74,15 @@ extension BalanceHomeAwayExpectations { } } -extension LeagueEntry.Runtime: Hashable { - public static func == (lhs: Self, rhs: Self) -> Bool { - lhs.id == rhs.id - && lhs.division == rhs.division - && lhs.gameDays == rhs.gameDays - && lhs.gameTimes == rhs.gameTimes - && lhs.gameLocations == rhs.gameLocations - && lhs.homeLocations == rhs.homeLocations - && lhs.byes == rhs.byes - && lhs.matchupsPerGameDay == rhs.matchupsPerGameDay +extension LeagueEntry.Runtime { + func isEqual(to rhs: Self) -> Bool { + id == rhs.id + && division == rhs.division + && gameDays == rhs.gameDays + //&& lhs.gameTimes == rhs.gameTimes + //&& lhs.gameLocations == rhs.gameLocations + //&& lhs.homeLocations == rhs.homeLocations + && byes == rhs.byes + //&& lhs.matchupsPerGameDay == rhs.matchupsPerGameDay } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift index 45dc6a7..083f8b8 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift @@ -7,10 +7,10 @@ struct DivisionEntryExpectations: ScheduleTestsProtocol { let matchupsPlayedPerDay:ContiguousArray> let assignedEntryHomeAways:AssignedEntryHomeAways let entryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay - let divisionEntries:[LeagueEntry.Runtime] func expectations( - balanceHomeAway: Bool + balanceHomeAway: Bool, + divisionEntries: [LeagueEntry.Runtime] ) { if balanceHomeAway { BalanceHomeAwayExpectations().expectations( diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift index 8069cd7..887bf6e 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift @@ -108,11 +108,11 @@ extension ScheduleExpectations { cap: cap, matchupsPlayedPerDay: matchupsPlayedPerDay, assignedEntryHomeAways: assignedEntryHomeAways, - entryMatchupsPerGameDay: entryMatchupsPerGameDay, - divisionEntries: divisionEntries + entryMatchupsPerGameDay: entryMatchupsPerGameDay ) divisionEntryExpectations.expectations( - balanceHomeAway: settings.general.balanceHomeAway + balanceHomeAway: settings.general.balanceHomeAway, + divisionEntries: divisionEntries ) } From 239b9453e2e9f3766e6d7ea3cbb6230908353ea9 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Fri, 6 Mar 2026 22:26:50 -0600 Subject: [PATCH 05/33] move some stuff around --- .../league-scheduling/data/AssignSlots.swift | 109 ------------------ .../data/AssignSlotsB2B.swift | 108 +++++++++++++++++ .../LeagueRequestPayload+Generate.swift | 101 ++-------------- .../LeagueRequestPayload+Validate.swift | 76 ++++++++++++ .../runtime/LeagueEntry+Runtime.swift | 4 - 5 files changed, 196 insertions(+), 202 deletions(-) create mode 100644 Sources/league-scheduling/data/AssignSlotsB2B.swift create mode 100644 Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Validate.swift diff --git a/Sources/league-scheduling/data/AssignSlots.swift b/Sources/league-scheduling/data/AssignSlots.swift index 60da107..9c620cb 100644 --- a/Sources/league-scheduling/data/AssignSlots.swift +++ b/Sources/league-scheduling/data/AssignSlots.swift @@ -123,115 +123,6 @@ extension LeagueScheduleData { } } -// MARK: Assign slots b2b -extension LeagueScheduleData { - private mutating func assignSlotsB2B( - canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) throws(LeagueError) -> Bool { - let slots = assignmentState.availableSlots - let assignmentStateCopy = assignmentState.copy() - whileLoop: while assignmentState.matchups.count != expectedMatchupsCount { - if Task.isCancelled { - throw LeagueError.timedOut(function: "assignSlotsB2B") - } - // TODO: pick the optimal combination that should be selected? - combinationLoop: for combination in allowedDivisionCombinations { - var assignedSlots = Set() - var combinationTimeAllocations:ContiguousArray> = .init( - repeating: .init(), - count: combination.first?.count ?? 10 - ) - for (divisionIndex, divisionCombination) in combination.enumerated() { - let division = LeagueDivision.IDValue(divisionIndex) - let divisionMatchups = assignmentState.allDivisionMatchups[unchecked: division] - assignmentState.availableMatchups = divisionMatchups - assignmentState.prioritizedEntries.removeAll(keepingCapacity: true) - for matchup in assignmentState.availableMatchups { - assignmentState.prioritizedEntries.insert(matchup.team1) - assignmentState.prioritizedEntries.insert(matchup.team2) - } - assignmentState.recalculateAllRemainingAllocations( - day: day, - entriesCount: entriesCount, - gameGap: gameGap, - canPlayAt: canPlayAt - ) - #if LOG - print("assignSlots;b2b;division=\(division);divisionCombination=\(divisionCombination);matchups.count=\(assignmentState.matchups.count);availableSlots=\(assignmentState.availableSlots.map({ $0.description }));remainingAllocations=\(assignmentState.remainingAllocations.map { $0.map({ $0.description }) })") - #endif - var disallowedTimes = BitSet64() - for (divisionCombinationIndex, amount) in divisionCombination.enumerated() { - guard amount > 0 else { continue } - let combinationTimeAllocation = combinationTimeAllocations[divisionCombinationIndex] - if !combinationTimeAllocation.isEmpty { - assignmentState.availableSlots = slots.filter { combinationTimeAllocation.contains($0.time) } - assignmentState.recalculateAvailableMatchups( - day: day, - entryMatchupsPerGameDay: defaultMaxEntryMatchupsPerGameDay, - allAvailableMatchups: divisionMatchups - ) - assignmentState.recalculateAllRemainingAllocations( - day: day, - entriesCount: entriesCount, - gameGap: gameGap, - canPlayAt: canPlayAt - ) - } - guard let matchups = assignBlockOfMatchups( - amount: amount, - division: division, - canPlayAt: canPlayAt - ) else { - assignmentState = assignmentStateCopy.copy() - #if LOG - print("assignSlotsB2B;failed to assign matchups for division \(division) and combination \(divisionCombination);skipping") - #endif - continue combinationLoop - } - for matchup in matchups { - disallowedTimes.insertMember(matchup.time) - combinationTimeAllocations[divisionCombinationIndex].insertMember(matchup.time) - assignedSlots.insert(matchup.slot) - } - assignmentState.availableSlots = slots.filter { !disallowedTimes.contains($0.time) } - assignmentState.recalculateAvailableMatchups( - day: day, - entryMatchupsPerGameDay: defaultMaxEntryMatchupsPerGameDay, - allAvailableMatchups: divisionMatchups - ) - assignmentState.recalculateAllRemainingAllocations( - day: day, - entriesCount: entriesCount, - gameGap: gameGap, - canPlayAt: canPlayAt - ) - #if LOG - print("assignSlots;b2b;combination=\(divisionCombination);assigned \(amount) for division \(division);availableSlots=\(assignmentState.availableSlots.map({ "\($0)" }))") - #endif - // successfully assigned matchup block of for - } - assignmentState.availableSlots = slots.filter { !assignedSlots.contains($0) } - assignmentState.recalculateAllRemainingAllocations( - day: day, - entriesCount: entriesCount, - gameGap: gameGap, - canPlayAt: canPlayAt - ) - #if LOG - print("assignSlots;b2b;assigned \(divisionCombination) for division \(division)") - #endif - } - break whileLoop - } - return false - } - #if LOG - print("assignSlotsB2B;assignmentState.matchups.count=\(assignmentState.matchups.count);expectedMatchupsCount=\(expectedMatchupsCount)") - #endif - return assignmentState.matchups.count == expectedMatchupsCount - } -} - // MARK: Select and assign matchup extension LeagueScheduleData { /// Selects and assigns a matchup to an available slot. diff --git a/Sources/league-scheduling/data/AssignSlotsB2B.swift b/Sources/league-scheduling/data/AssignSlotsB2B.swift new file mode 100644 index 0000000..5467a0a --- /dev/null +++ b/Sources/league-scheduling/data/AssignSlotsB2B.swift @@ -0,0 +1,108 @@ + +extension LeagueScheduleData { + mutating func assignSlotsB2B( + canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable + ) throws(LeagueError) -> Bool { + let slots = assignmentState.availableSlots + let assignmentStateCopy = assignmentState.copy() + whileLoop: while assignmentState.matchups.count != expectedMatchupsCount { + if Task.isCancelled { + throw LeagueError.timedOut(function: "assignSlotsB2B") + } + // TODO: pick the optimal combination that should be selected? + combinationLoop: for combination in allowedDivisionCombinations { + var assignedSlots = Set() + var combinationTimeAllocations:ContiguousArray> = .init( + repeating: .init(), + count: combination.first?.count ?? 10 + ) + for (divisionIndex, divisionCombination) in combination.enumerated() { + let division = LeagueDivision.IDValue(divisionIndex) + let divisionMatchups = assignmentState.allDivisionMatchups[unchecked: division] + assignmentState.availableMatchups = divisionMatchups + assignmentState.prioritizedEntries.removeAll(keepingCapacity: true) + for matchup in assignmentState.availableMatchups { + assignmentState.prioritizedEntries.insert(matchup.team1) + assignmentState.prioritizedEntries.insert(matchup.team2) + } + assignmentState.recalculateAllRemainingAllocations( + day: day, + entriesCount: entriesCount, + gameGap: gameGap, + canPlayAt: canPlayAt + ) + #if LOG + print("assignSlots;b2b;division=\(division);divisionCombination=\(divisionCombination);matchups.count=\(assignmentState.matchups.count);availableSlots=\(assignmentState.availableSlots.map({ $0.description }));remainingAllocations=\(assignmentState.remainingAllocations.map { $0.map({ $0.description }) })") + #endif + var disallowedTimes = BitSet64() + for (divisionCombinationIndex, amount) in divisionCombination.enumerated() { + guard amount > 0 else { continue } + let combinationTimeAllocation = combinationTimeAllocations[divisionCombinationIndex] + if !combinationTimeAllocation.isEmpty { + assignmentState.availableSlots = slots.filter { combinationTimeAllocation.contains($0.time) } + assignmentState.recalculateAvailableMatchups( + day: day, + entryMatchupsPerGameDay: defaultMaxEntryMatchupsPerGameDay, + allAvailableMatchups: divisionMatchups + ) + assignmentState.recalculateAllRemainingAllocations( + day: day, + entriesCount: entriesCount, + gameGap: gameGap, + canPlayAt: canPlayAt + ) + } + guard let matchups = assignBlockOfMatchups( + amount: amount, + division: division, + canPlayAt: canPlayAt + ) else { + assignmentState = assignmentStateCopy.copy() + #if LOG + print("assignSlotsB2B;failed to assign matchups for division \(division) and combination \(divisionCombination);skipping") + #endif + continue combinationLoop + } + for matchup in matchups { + disallowedTimes.insertMember(matchup.time) + combinationTimeAllocations[divisionCombinationIndex].insertMember(matchup.time) + assignedSlots.insert(matchup.slot) + } + assignmentState.availableSlots = slots.filter { !disallowedTimes.contains($0.time) } + assignmentState.recalculateAvailableMatchups( + day: day, + entryMatchupsPerGameDay: defaultMaxEntryMatchupsPerGameDay, + allAvailableMatchups: divisionMatchups + ) + assignmentState.recalculateAllRemainingAllocations( + day: day, + entriesCount: entriesCount, + gameGap: gameGap, + canPlayAt: canPlayAt + ) + #if LOG + print("assignSlots;b2b;combination=\(divisionCombination);assigned \(amount) for division \(division);availableSlots=\(assignmentState.availableSlots.map({ "\($0)" }))") + #endif + // successfully assigned matchup block of for + } + assignmentState.availableSlots = slots.filter { !assignedSlots.contains($0) } + assignmentState.recalculateAllRemainingAllocations( + day: day, + entriesCount: entriesCount, + gameGap: gameGap, + canPlayAt: canPlayAt + ) + #if LOG + print("assignSlots;b2b;assigned \(divisionCombination) for division \(division)") + #endif + } + break whileLoop + } + return false + } + #if LOG + print("assignSlotsB2B;assignmentState.matchups.count=\(assignmentState.matchups.count);expectedMatchupsCount=\(expectedMatchupsCount)") + #endif + return assignmentState.matchups.count == expectedMatchupsCount + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index 7415064..4bf5ede 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -132,7 +132,7 @@ extension LeagueRequestPayload { defaultTimes: Times, defaultLocations: Locations ) async throws(LeagueError) -> LeagueGenerationResult { - let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) + let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) var teamsForDivision = [Int](repeating: 0, count: divisionsCount) let entries = try parseEntries( divisionsCount: divisionsCount, @@ -234,24 +234,23 @@ extension LeagueRequestPayload { // MARK: Division defaults extension LeagueRequestPayload { - struct DivisionDefaults: Sendable, ~Copyable { + struct DivisionDefaults: Sendable, ~Copyable { let gameDays:[Set] let byes:[Set] let gameTimes:[[Times]] - let gameLocations:[[BitSet64]] + let gameLocations:[[Locations]] } } // MARK: Load division defaults extension LeagueRequestPayload { - // TODO: fix (pass the generic times and locations) - private func loadDivisionDefaults( + private func loadDivisionDefaults( divisionsCount: Int - ) -> DivisionDefaults { + ) -> DivisionDefaults { var gameDays = [Set]() var byes = [Set]() var gameTimes = [[Times]]() - var gameLocations = [[BitSet64]]() + var gameLocations = [[Locations]]() gameDays.reserveCapacity(divisionsCount) byes.reserveCapacity(divisionsCount) gameTimes.reserveCapacity(divisionsCount) @@ -293,7 +292,7 @@ extension LeagueRequestPayload { if division.hasGameDayLocations { gameLocations.append(division.gameDayLocations.locations.map({ .init($0.locations) })) } else { - var dgdl = [BitSet64]() + var dgdl = [Locations]() for gameDay in 0..]() + var dgdl = [Locations]() for gameDay in 0..( + private func parseEntries( divisionsCount: Int, teams: [LeagueEntry], teamsForDivision: inout [Int], - divisionDefaults: borrowing DivisionDefaults + divisionDefaults: borrowing DivisionDefaults ) throws(LeagueError) -> [LeagueEntry.Runtime] { var entries = [LeagueEntry.Runtime]() entries.reserveCapacity(teams.count) @@ -375,6 +374,7 @@ extension LeagueRequestPayload { let division = min(team.division, UInt32(divisionsCount - 1)) // TODO: fix let defaultGameTimes = divisionDefaults.gameTimes[unchecked: division].map { BitSet64.init($0) } + let defaultGameLocations = divisionDefaults.gameLocations[unchecked: division].map { BitSet64.init($0) } teamsForDivision[Int(division)] += 1 entries.append(team.runtime( id: LeagueEntry.IDValue(i), @@ -382,7 +382,7 @@ extension LeagueRequestPayload { defaultGameDays: divisionDefaults.gameDays[unchecked: division], defaultByes: divisionDefaults.byes[unchecked: division], defaultGameTimes: defaultGameTimes, - defaultGameLocations: divisionDefaults.gameLocations[unchecked: division] + defaultGameLocations: defaultGameLocations )) } return entries @@ -484,83 +484,6 @@ extension LeagueRequestPayload { } } -// MARK: Validate settings -extension LeagueRequestPayload { - @discardableResult - private func validateSettings( - kind: String, - settings: LeagueGeneralSettings, - fallbackSettings: LeagueGeneralSettings - ) throws(LeagueError) -> GameGap? { - let isDefault = kind == "default" - if isDefault || settings.hasTimeSlots { - guard settings.timeSlots > 0 else { - throw .malformedInput(msg: "\(kind) 'timeSlots' size needs to be > 0") - } - } - if settings.hasStartingTimes { - guard settings.startingTimes.times.count > 0 else { - throw .malformedInput(msg: "\(kind) 'startingTimes' size needs to be > 0") - } - } - if settings.hasTimeSlots && settings.hasStartingTimes { - guard settings.timeSlots == settings.startingTimes.times.count else { - throw .malformedInput(msg: "\(kind) 'timeSlots' and 'startingTimes' size need to be equal") - } - } - if isDefault || settings.hasLocations { - guard settings.locations > 0 else { - throw .malformedInput(msg: "\(kind) 'locations' needs to be > 0") - } - } - if isDefault || settings.hasEntryMatchupsPerGameDay { - guard settings.entryMatchupsPerGameDay > 0 else { - throw .malformedInput(msg: "\(kind) 'entryMatchupsPerGameDay' needs to be > 0") - } - } - if settings.hasMaximumPlayableMatchups { - guard settings.maximumPlayableMatchups.array.count == entries.count else { - throw .malformedInput(msg: "\(kind) 'maximumPlayableMatchups' size != \(entries.count)") - } - } - if isDefault || settings.hasEntriesPerLocation { - guard settings.entriesPerLocation > 0 else { - throw .malformedInput(msg: "\(kind) 'entriesPerLocation' needs to be > 0") - } - } - let locations = settings.hasLocations ? settings.locations : fallbackSettings.locations - if settings.hasLocationTravelDurations { - guard settings.locationTravelDurations.locations.count == locations else { - throw .malformedInput(msg: "\(kind) 'locationTravelDurations.locations' size != \(locations)") - } - } - if settings.hasLocationTimeExclusivities { - guard settings.locationTimeExclusivities.locations.count == locations else { - throw .malformedInput(msg: "\(kind) 'locationTimeExclusivities.locations' size != \(locations)") - } - } - if settings.hasRedistributionSettings { - if settings.redistributionSettings.hasMinMatchupsRequired { - guard settings.redistributionSettings.minMatchupsRequired > 0 else { - throw .malformedInput(msg: "\(kind) redistribution setting 'minMatchupsRequired' needs to be > 0") - } - } - if settings.redistributionSettings.hasMaxMovableMatchups { - guard settings.redistributionSettings.maxMovableMatchups > 0 else { - throw .malformedInput(msg: "\(kind) redistribution setting 'maxMovableMatchups' needs to be > 0") - } - } - } - if isDefault || settings.hasGameGap { - guard let gameGap = GameGap.init(htmlInputValue: settings.gameGap) else { - throw .malformedInput(msg: "\(kind) invalid 'gameGap' value: \(settings.gameGap)") - } - return gameGap - } - return nil - } -} - // MARK: Calculate maximum same opponent matchups cap extension LeagueRequestPayload { func calculateMaximumSameOpponentMatchupsCap( diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Validate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Validate.swift new file mode 100644 index 0000000..01fdd22 --- /dev/null +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Validate.swift @@ -0,0 +1,76 @@ + +extension LeagueRequestPayload { + @discardableResult + func validateSettings( + kind: String, + settings: LeagueGeneralSettings, + fallbackSettings: LeagueGeneralSettings + ) throws(LeagueError) -> GameGap? { + let isDefault = kind == "default" + if isDefault || settings.hasTimeSlots { + guard settings.timeSlots > 0 else { + throw .malformedInput(msg: "\(kind) 'timeSlots' size needs to be > 0") + } + } + if settings.hasStartingTimes { + guard settings.startingTimes.times.count > 0 else { + throw .malformedInput(msg: "\(kind) 'startingTimes' size needs to be > 0") + } + } + if settings.hasTimeSlots && settings.hasStartingTimes { + guard settings.timeSlots == settings.startingTimes.times.count else { + throw .malformedInput(msg: "\(kind) 'timeSlots' and 'startingTimes' size need to be equal") + } + } + if isDefault || settings.hasLocations { + guard settings.locations > 0 else { + throw .malformedInput(msg: "\(kind) 'locations' needs to be > 0") + } + } + if isDefault || settings.hasEntryMatchupsPerGameDay { + guard settings.entryMatchupsPerGameDay > 0 else { + throw .malformedInput(msg: "\(kind) 'entryMatchupsPerGameDay' needs to be > 0") + } + } + if settings.hasMaximumPlayableMatchups { + guard settings.maximumPlayableMatchups.array.count == entries.count else { + throw .malformedInput(msg: "\(kind) 'maximumPlayableMatchups' size != \(entries.count)") + } + } + if isDefault || settings.hasEntriesPerLocation { + guard settings.entriesPerLocation > 0 else { + throw .malformedInput(msg: "\(kind) 'entriesPerLocation' needs to be > 0") + } + } + let locations = settings.hasLocations ? settings.locations : fallbackSettings.locations + if settings.hasLocationTravelDurations { + guard settings.locationTravelDurations.locations.count == locations else { + throw .malformedInput(msg: "\(kind) 'locationTravelDurations.locations' size != \(locations)") + } + } + if settings.hasLocationTimeExclusivities { + guard settings.locationTimeExclusivities.locations.count == locations else { + throw .malformedInput(msg: "\(kind) 'locationTimeExclusivities.locations' size != \(locations)") + } + } + if settings.hasRedistributionSettings { + if settings.redistributionSettings.hasMinMatchupsRequired { + guard settings.redistributionSettings.minMatchupsRequired > 0 else { + throw .malformedInput(msg: "\(kind) redistribution setting 'minMatchupsRequired' needs to be > 0") + } + } + if settings.redistributionSettings.hasMaxMovableMatchups { + guard settings.redistributionSettings.maxMovableMatchups > 0 else { + throw .malformedInput(msg: "\(kind) redistribution setting 'maxMovableMatchups' needs to be > 0") + } + } + } + if isDefault || settings.hasGameGap { + guard let gameGap = GameGap.init(htmlInputValue: settings.gameGap) else { + throw .malformedInput(msg: "\(kind) invalid 'gameGap' value: \(settings.gameGap)") + } + return gameGap + } + return nil + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift index 97cd8df..898f71f 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift @@ -87,10 +87,6 @@ extension LeagueEntry { self.matchupsPerGameDay = matchupsPerGameDay } - func hash(into hasher: inout Hasher) { - hasher.combine(id) - } - /// - Returns: Maximum number of matchups this entry can play on the given day index. func maxMatchupsForGameDay( day: LeagueDayIndex, From e48b6aab5aa725b05432737d4119f5504f34b403 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Sat, 7 Mar 2026 02:06:38 -0600 Subject: [PATCH 06/33] handle generics better --- .../league-scheduling/LeagueSchedule.swift | 28 +-- Sources/league-scheduling/Leagues.swift | 23 +++ .../league-scheduling/data/AssignSlots.swift | 4 +- .../data/BalanceHomeAway.swift | 4 +- .../data/DivisionMatchupCombinations.swift | 182 +++++++++--------- .../data/LeagueScheduleData.swift | 18 +- .../data/LeagueScheduleDataSnapshot.swift | 8 +- .../league-scheduling/data/MatchupBlock.swift | 34 +--- .../league-scheduling/data/Redistribute.swift | 4 +- .../data/RedistributionData.swift | 10 +- .../assignmentState/AssignmentState.swift | 20 +- .../ScheduleConfiguration.swift | 11 ++ .../LeagueRequestPayload+Generate.swift | 34 ++-- .../runtime/LeagueEntry+Runtime.swift | 27 ++- .../LeagueGeneralSettings+Runtime.swift | 19 +- ...eagueGeneralSettings+RuntimeProtocol.swift | 62 +----- .../LeagueRequestPayload+Runtime.swift | 35 +--- .../DivisionMatchupCombinationTests.swift | 14 +- .../LeagueSchedulingTests/League3Tests.swift | 2 +- .../MatchupBlockTests.swift | 20 +- .../schedules/ScheduleBack2Back.swift | 2 +- .../schedules/ScheduleBeanBagToss.swift | 2 +- .../schedules/ScheduleMisc.swift | 2 +- .../schedules/ScheduleSameLocationIfB2B.swift | 6 +- .../BalanceHomeAwayExpectations.swift | 6 +- .../expectations/DayExpectations.swift | 4 +- .../DivisionEntryExpectations.swift | 6 +- .../expectations/ScheduleExpectations.swift | 12 +- .../util/ScheduleTestsProtocol.swift | 17 +- 29 files changed, 271 insertions(+), 345 deletions(-) create mode 100644 Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift diff --git a/Sources/league-scheduling/LeagueSchedule.swift b/Sources/league-scheduling/LeagueSchedule.swift index d62c6a2..8ab738d 100644 --- a/Sources/league-scheduling/LeagueSchedule.swift +++ b/Sources/league-scheduling/LeagueSchedule.swift @@ -7,7 +7,9 @@ import Foundation // TODO: support divisions on the same day with different times enum LeagueSchedule: Sendable, ~Copyable { - static func generate(_ settings: some LeagueRequestPayload.RuntimeProtocol) async -> LeagueGenerationResult { + static func generate( + _ settings: LeagueRequestPayload.Runtime + ) async -> LeagueGenerationResult { var err:String? = nil var results = [LeagueGenerationData]() do { @@ -33,8 +35,8 @@ enum LeagueSchedule: Sendable, ~Copyable { // MARK: Generate schedules extension LeagueSchedule { - static func generateSchedules( - settings: some LeagueRequestPayload.RuntimeProtocol + static func generateSchedules( + settings: LeagueRequestPayload.Runtime ) async throws -> [LeagueGenerationData] { let divisionsCount = settings.divisions.count var divisionEntries:ContiguousArray> = .init(repeating: Set(), count: divisionsCount) @@ -62,7 +64,7 @@ extension LeagueSchedule { divisionEntries: divisionEntries, divisions: settings.divisions ) - let dataSnapshot = LeagueScheduleDataSnapshot( + let dataSnapshot = LeagueScheduleDataSnapshot( maxStartingTimes: maxStartingTimes, startingTimes: settings.general.startingTimes, maxLocations: maxLocations, @@ -141,10 +143,10 @@ extension LeagueSchedule { // MARK: Generate schedule extension LeagueSchedule { - private static func generateSchedule( + private static func generateSchedule( dayOfWeek: LeagueDayOfWeek, - settings: some LeagueRequestPayload.RuntimeProtocol, - dataSnapshot: LeagueScheduleDataSnapshot, + settings: LeagueRequestPayload.Runtime, + dataSnapshot: LeagueScheduleDataSnapshot, divisionsCount: Int, maxStartingTimes: LeagueTimeIndex, maxLocations: LeagueLocationIndex, @@ -167,7 +169,7 @@ extension LeagueSchedule { scheduledEntries: scheduledEntries ) - var snapshots = [LeagueScheduleDataSnapshot]() + var snapshots = [LeagueScheduleDataSnapshot]() snapshots.reserveCapacity(gameDays) var gameDayRegenerationAttempt:LeagueRegenerationAttempt = 0 var day:LeagueDayIndex = 0 @@ -251,9 +253,9 @@ extension LeagueSchedule { finalizeGenerationData(generationData: &generationData, data: data) return generationData } - private static func finalizeGenerationData( + private static func finalizeGenerationData( generationData: inout LeagueGenerationData, - data: borrowing LeagueScheduleData + data: borrowing LeagueScheduleData ) { #if UnitTesting generationData.assignedTimes = data.assignmentState.assignedTimes @@ -271,10 +273,10 @@ extension LeagueSchedule { // MARK: Load max allocations extension LeagueSchedule { - static func loadMaxAllocations( - dataSnapshot: inout LeagueScheduleDataSnapshot, + static func loadMaxAllocations( + dataSnapshot: inout LeagueScheduleDataSnapshot, gameDayDivisionEntries: inout ContiguousArray>>, - settings: some LeagueRequestPayload.RuntimeProtocol, + settings: LeagueRequestPayload.Runtime, maxStartingTimes: LeagueTimeIndex, maxLocations: LeagueLocationIndex, scheduledEntries: Set diff --git a/Sources/league-scheduling/Leagues.swift b/Sources/league-scheduling/Leagues.swift index 2265f4e..d78c33d 100644 --- a/Sources/league-scheduling/Leagues.swift +++ b/Sources/league-scheduling/Leagues.swift @@ -122,4 +122,27 @@ func optimalTimeSlots( print("LeagueSchedule;optimalTimeSlots;availableTimeSlots=\(availableTimeSlots);locations=\(locations);matchupsCount=\(matchupsCount);totalMatchupsPlayed=\(totalMatchupsPlayed);filledTimes=\(filledTimes)") #endif return min(availableTimeSlots, filledTimes) +} + +func calculateAdjacentTimes( + for time: LeagueTimeIndex, + entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay +) -> TimeSet { + var adjacentTimes = TimeSet() + let timeIndex = time % entryMatchupsPerGameDay + if timeIndex == 0 { + for i in 1.., allAvailableMatchups: Set, - assignmentState: inout AssignmentState, + assignmentState: inout AssignmentState, shouldSkipSelection: (LeagueMatchupPair) -> Bool, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable @@ -187,7 +187,7 @@ extension LeagueScheduleData { entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, divisionRecurringDayLimitInterval: ContiguousArray, allAvailableMatchups: Set, - assignmentState: inout AssignmentState, + assignmentState: inout AssignmentState, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) -> LeagueMatchup? { diff --git a/Sources/league-scheduling/data/BalanceHomeAway.swift b/Sources/league-scheduling/data/BalanceHomeAway.swift index eebda75..97fdc3a 100644 --- a/Sources/league-scheduling/data/BalanceHomeAway.swift +++ b/Sources/league-scheduling/data/BalanceHomeAway.swift @@ -1,8 +1,8 @@ extension LeagueMatchupPair { /// Balances home/away allocations, mutating `team1` (home) and `team2` (away) if necessary. - mutating func balanceHomeAway( - assignmentState: borrowing AssignmentState + mutating func balanceHomeAway( + assignmentState: borrowing AssignmentState ) { let team1GamesPlayedAgainstTeam2 = assignmentState.assignedEntryHomeAways[unchecked: team1][unchecked: team2] // TODO: fix; more/less opponents than game days can make this unbalanced diff --git a/Sources/league-scheduling/data/DivisionMatchupCombinations.swift b/Sources/league-scheduling/data/DivisionMatchupCombinations.swift index 8b21d60..e5b7258 100644 --- a/Sources/league-scheduling/data/DivisionMatchupCombinations.swift +++ b/Sources/league-scheduling/data/DivisionMatchupCombinations.swift @@ -1,110 +1,106 @@ // MARK: All combinations -extension LeagueScheduleData { - /// - Returns: All division matchup combinations separated by division. - /// - Usage: [`LeagueDivision.IDValue`: `division matchup combinations`] - static func allDivisionMatchupCombinations( - entriesPerMatchup: LeagueEntriesPerMatchup, - locations: LeagueLocationIndex, - entryCountsForDivision: ContiguousArray - ) -> ContiguousArray>> { - var combinations:ContiguousArray>> = .init(repeating: [], count: entryCountsForDivision.count) - for (divisionIndex, entryCount) in entryCountsForDivision.enumerated() { - if entryCount > 0 { - let matchupsCount = entryCount / entriesPerMatchup - let upperLimit:Int - if matchupsCount > locations { // more available matchups than locations - upperLimit = Int(locations) - } else { - upperLimit = matchupsCount - } - for i in 0...upperLimit { - let right = matchupsCount - i - if i != 1 && right != 1 && right <= upperLimit { - var combo = ContiguousArray() - combo.append(i) - combo.append(right) - combinations[divisionIndex].append(combo) - } - } +/// - Returns: All division matchup combinations separated by division. +/// - Usage: [`LeagueDivision.IDValue`: `division matchup combinations`] +func allDivisionMatchupCombinations( + entriesPerMatchup: LeagueEntriesPerMatchup, + locations: LeagueLocationIndex, + entryCountsForDivision: ContiguousArray +) -> ContiguousArray>> { + var combinations:ContiguousArray>> = .init(repeating: [], count: entryCountsForDivision.count) + for (divisionIndex, entryCount) in entryCountsForDivision.enumerated() { + if entryCount > 0 { + let matchupsCount = entryCount / entriesPerMatchup + let upperLimit:Int + if matchupsCount > locations { // more available matchups than locations + upperLimit = Int(locations) } else { - combinations[divisionIndex] = [] + upperLimit = matchupsCount } + for i in 0...upperLimit { + let right = matchupsCount - i + if i != 1 && right != 1 && right <= upperLimit { + var combo = ContiguousArray() + combo.append(i) + combo.append(right) + combinations[divisionIndex].append(combo) + } + } + } else { + combinations[divisionIndex] = [] } - return combinations } + return combinations } // MARK: Allowed combinations -extension LeagueScheduleData { - /// - Returns: Allowed division matchup combinations - /// - Usage: [`allowed matchup combination index`: [`LeagueDivision.IDValue`: `division matchup combination`]] - static func allowedDivisionMatchupCombinations( - entriesPerMatchup: LeagueEntriesPerMatchup, - locations: LeagueLocationIndex, - entryCountsForDivision: ContiguousArray - ) -> ContiguousArray>> { - let allCombinations = allDivisionMatchupCombinations( - entriesPerMatchup: entriesPerMatchup, - locations: locations, - entryCountsForDivision: entryCountsForDivision - ) - var combinations = ContiguousArray>>() - guard let initialResultsCount = allCombinations.first?.first?.count else { return combinations } - var combinationBuilder = ContiguousArray>() - combinationBuilder.reserveCapacity(entryCountsForDivision.count) +/// - Returns: Allowed division matchup combinations +/// - Usage: [`allowed matchup combination index`: [`LeagueDivision.IDValue`: `division matchup combination`]] +func allowedDivisionMatchupCombinations( + entriesPerMatchup: LeagueEntriesPerMatchup, + locations: LeagueLocationIndex, + entryCountsForDivision: ContiguousArray +) -> ContiguousArray>> { + let allCombinations = allDivisionMatchupCombinations( + entriesPerMatchup: entriesPerMatchup, + locations: locations, + entryCountsForDivision: entryCountsForDivision + ) + var combinations = ContiguousArray>>() + guard let initialResultsCount = allCombinations.first?.first?.count else { return combinations } + var combinationBuilder = ContiguousArray>() + combinationBuilder.reserveCapacity(entryCountsForDivision.count) + yieldAllowedCombinations( + allCombinations: allCombinations, + division: 0, + locations: locations, + results: .init(repeating: 0, count: initialResultsCount), + combinationBuilder: combinationBuilder + ) { + combinations.append($0) + } + return combinations +} +private func yieldAllowedCombinations( + allCombinations: ContiguousArray>>, + division: LeagueDivision.IDValue, + locations: LeagueLocationIndex, + results: ContiguousArray, + combinationBuilder: ContiguousArray>, + yield: (_ combination: ContiguousArray>) -> Void +) { + guard let targetCombinations = allCombinations[uncheckedPositive: division] else { + yield(combinationBuilder) + return + } + guard !targetCombinations.isEmpty else { yieldAllowedCombinations( allCombinations: allCombinations, - division: 0, + division: division + 1, locations: locations, - results: .init(repeating: 0, count: initialResultsCount), - combinationBuilder: combinationBuilder - ) { - combinations.append($0) - } - return combinations + results: results, + combinationBuilder: combinationBuilder, + yield: yield + ) + return } - private static func yieldAllowedCombinations( - allCombinations: ContiguousArray>>, - division: LeagueDivision.IDValue, - locations: LeagueLocationIndex, - results: ContiguousArray, - combinationBuilder: ContiguousArray>, - yield: (_ combination: ContiguousArray>) -> Void - ) { - guard let targetCombinations = allCombinations[uncheckedPositive: division] else { - yield(combinationBuilder) - return - } - guard !targetCombinations.isEmpty else { - yieldAllowedCombinations( - allCombinations: allCombinations, - division: division + 1, - locations: locations, - results: results, - combinationBuilder: combinationBuilder, - yield: yield - ) - return - } - combinationLoop: - for combination in targetCombinations { - let combined = zip(results, combination).map { $0 + $1 } - for value in combined { - if value > locations { - continue combinationLoop - } + combinationLoop: + for combination in targetCombinations { + let combined = zip(results, combination).map { $0 + $1 } + for value in combined { + if value > locations { + continue combinationLoop } - var builder = combinationBuilder - builder.append(combination) - yieldAllowedCombinations( - allCombinations: allCombinations, - division: division + 1, - locations: locations, - results: ContiguousArray(combined), - combinationBuilder: builder, - yield: yield - ) } + var builder = combinationBuilder + builder.append(combination) + yieldAllowedCombinations( + allCombinations: allCombinations, + division: division + 1, + locations: locations, + results: ContiguousArray(combined), + combinationBuilder: builder, + yield: yield + ) } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index afc6385..2f5a268 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -3,7 +3,7 @@ import StaticDateTimes // MARK: Data /// Fundamental building block that keeps track of and enforces assignment rules when building the schedule. -struct LeagueScheduleData: Sendable, ~Copyable { +struct LeagueScheduleData: Sendable, ~Copyable { let clock = ContinuousClock() let entriesPerMatchup:LeagueEntriesPerMatchup let entriesCount:Int @@ -30,17 +30,17 @@ struct LeagueScheduleData: Sendable, ~Copyable { /// - Usage: [`selection index` : `Set`] var failedMatchupSelections:ContiguousArray> - var assignmentState:AssignmentState + var assignmentState:AssignmentState var prioritizeEarlierTimes:Bool var executionSteps = [ExecutionStep]() var shuffleHistory = [LeagueShuffleAction]() - var redistributionData:RedistributionData? + var redistributionData:RedistributionData? var redistributedMatchups = false init( - snapshot: LeagueScheduleDataSnapshot + snapshot: LeagueScheduleDataSnapshot ) { //locations = snapshot.locations entriesPerMatchup = snapshot.entriesPerMatchup @@ -62,7 +62,7 @@ struct LeagueScheduleData: Sendable, ~Copyable { // MARK: Snapshot extension LeagueScheduleData { - mutating func loadSnapshot(_ snapshot: LeagueScheduleDataSnapshot) { + mutating func loadSnapshot(_ snapshot: LeagueScheduleDataSnapshot) { //locations = snapshot.locations divisionRecurringDayLimitInterval = snapshot.divisionRecurringDayLimitInterval day = snapshot.day @@ -77,7 +77,7 @@ extension LeagueScheduleData { shuffleHistory = snapshot.shuffleHistory } - func snapshot() -> LeagueScheduleDataSnapshot { + func snapshot() -> LeagueScheduleDataSnapshot { return .init(self) } } @@ -92,10 +92,10 @@ extension LeagueScheduleData { /// - entryMatchupsPerGameDay: Number of times a single team will play on `day`. mutating func newDay( day: LeagueDayIndex, - daySettings: some LeagueGeneralSettings.RuntimeProtocol, + daySettings: LeagueGeneralSettings.Runtime, divisionEntries: ContiguousArray>, availableSlots: Set, - settings: some LeagueRequestPayload.RuntimeProtocol, + settings: LeagueRequestPayload.Runtime, generationData: inout LeagueGenerationData ) throws(LeagueError) { let now = clock.now @@ -141,7 +141,7 @@ extension LeagueScheduleData { expectedMatchupsCount = min(availableSlots.count, expectedMatchupsCount) assignmentState.availableSlots = availableSlots if daySettings.gameGap == .no { - allowedDivisionCombinations = Self.allowedDivisionMatchupCombinations( + allowedDivisionCombinations = allowedDivisionMatchupCombinations( entriesPerMatchup: entriesPerMatchup, locations: daySettings.locations, entryCountsForDivision: entryCountsForDivision diff --git a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift index fc33abf..dc0fd88 100644 --- a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift +++ b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift @@ -1,7 +1,7 @@ import StaticDateTimes -struct LeagueScheduleDataSnapshot: Sendable { +struct LeagueScheduleDataSnapshot: Sendable { let entriesPerMatchup:LeagueEntriesPerMatchup let entriesCount:Int let entryDivisions:ContiguousArray @@ -21,7 +21,7 @@ struct LeagueScheduleDataSnapshot: Sendable { /// - Usage: [`selection index` : `Set`] var failedMatchupSelections:ContiguousArray> - var assignmentState:AssignmentStateCopyable + var assignmentState:AssignmentStateCopyable var prioritizeEarlierTimes = false var executionSteps = [ExecutionStep]() @@ -33,7 +33,7 @@ struct LeagueScheduleDataSnapshot: Sendable { maxLocations: LeagueLocationIndex, entriesPerMatchup: LeagueEntriesPerMatchup, maximumPlayableMatchups: [UInt32], - entries: [LeagueEntry.Runtime], + entries: [Config.EntryRuntime], divisionEntries: ContiguousArray>, matchupDuration: LeagueMatchupDuration, gameGap: (Int, Int), @@ -87,7 +87,7 @@ struct LeagueScheduleDataSnapshot: Sendable { ) } - init(_ snapshot: borrowing LeagueScheduleData) { + init(_ snapshot: borrowing LeagueScheduleData) { entriesPerMatchup = snapshot.entriesPerMatchup entriesCount = snapshot.entriesCount entryDivisions = snapshot.entryDivisions diff --git a/Sources/league-scheduling/data/MatchupBlock.swift b/Sources/league-scheduling/data/MatchupBlock.swift index 3c7369f..7f5a4b8 100644 --- a/Sources/league-scheduling/data/MatchupBlock.swift +++ b/Sources/league-scheduling/data/MatchupBlock.swift @@ -53,7 +53,7 @@ extension LeagueScheduleData { gameGap: GameGap.TupleValue, entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, divisionRecurringDayLimitInterval: ContiguousArray, - assignmentState: inout AssignmentState, + assignmentState: inout AssignmentState, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) -> Set? { @@ -92,7 +92,7 @@ extension LeagueScheduleData { selectSlot: selectSlot, canPlayAt: canPlayAt ) else { return nil } - adjacentTimes = Self.adjacentTimes(for: firstMatchup.time, entryMatchupsPerGameDay: entryMatchupsPerGameDay) + adjacentTimes = calculateAdjacentTimes(for: firstMatchup.time, entryMatchupsPerGameDay: entryMatchupsPerGameDay) localAssignmentState.availableSlots = localAssignmentState.availableSlots.filter { $0.time == firstMatchup.time } localAssignmentState.recalculateAllRemainingAllocations( day: day, @@ -222,7 +222,7 @@ extension LeagueScheduleData { entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, divisionRecurringDayLimitInterval: ContiguousArray, allAvailableMatchups: Set, - localAssignmentState: inout AssignmentState, + localAssignmentState: inout AssignmentState, remainingPrioritizedEntries: inout Set, selectedEntries: inout Set, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, @@ -259,7 +259,7 @@ extension LeagueScheduleData { entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, divisionRecurringDayLimitInterval: ContiguousArray, allAvailableMatchups: Set, - localAssignmentState: inout AssignmentState, + localAssignmentState: inout AssignmentState, shouldSkipSelection: (LeagueMatchupPair) -> Bool, remainingPrioritizedEntries: inout Set, selectedEntries: inout Set, @@ -289,30 +289,4 @@ extension LeagueScheduleData { selectedEntries.insert(leagueMatchup.away) return leagueMatchup } -} - -// MARK: Adjacent times -extension LeagueScheduleData { - static func adjacentTimes( - for time: LeagueTimeIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay - ) -> BitSet64 { - var adjacentTimes = BitSet64() - let timeIndex = time % entryMatchupsPerGameDay - if timeIndex == 0 { - for i in 1.., generationData: inout LeagueGenerationData ) throws(LeagueError) { guard day > 0 else { @@ -38,7 +38,7 @@ extension LeagueScheduleData { mutating func tryRedistributing( startDayIndex: LeagueDayIndex, - settings: some LeagueRequestPayload.RuntimeProtocol, + settings: LeagueRequestPayload.Runtime, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable, generationData: inout LeagueGenerationData ) throws(LeagueError) { diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index f26ef29..bbb317a 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -1,5 +1,5 @@ -struct RedistributionData: Sendable { +struct RedistributionData: Sendable { /// The latest `LeagueDayIndex` that is allowed to redistribute matchups from. let startDayIndex:LeagueDayIndex let entryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay @@ -14,7 +14,7 @@ struct RedistributionData: Sendable { dayIndex: LeagueDayIndex, startDayIndex: LeagueDayIndex, settings: LitLeagues_Leagues_RedistributionSettings?, - data: borrowing LeagueScheduleData + data: borrowing LeagueScheduleData ) { self.startDayIndex = startDayIndex self.entryMatchupsPerGameDay = data.defaultMaxEntryMatchupsPerGameDay @@ -42,7 +42,7 @@ extension RedistributionData { canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable, day: LeagueDayIndex, gameGap: GameGap.TupleValue, - assignmentState: inout AssignmentState, + assignmentState: inout AssignmentState, executionSteps: inout [ExecutionStep], generationData: inout LeagueGenerationData ) -> Bool { @@ -134,7 +134,7 @@ extension RedistributionData { extension RedistributionData { private func selectRedistributable( from redistributables: Set, - assignmentState: borrowing AssignmentState, + assignmentState: borrowing AssignmentState, generationData: LeagueGenerationData ) -> Redistributable? { var redistributable:Redistributable? = nil @@ -179,7 +179,7 @@ extension RedistributionData { extension RedistributionData { private mutating func redistribute( redistributable: inout Redistributable, - assignmentState: inout AssignmentState, + assignmentState: inout AssignmentState, generationData: inout LeagueGenerationData ) { generationData.schedule[unchecked: redistributable.fromDay].remove(redistributable.matchup) diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift index a5d6a8a..529053c 100644 --- a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift +++ b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift @@ -2,8 +2,8 @@ import StaticDateTimes // MARK: Noncopyable -struct AssignmentState: Sendable, ~Copyable { - let entries:[LeagueEntry.Runtime] +struct AssignmentState: Sendable, ~Copyable { + let entries:[Config.EntryRuntime] var startingTimes:[StaticTime] var matchupDuration:LeagueMatchupDuration var locationTravelDurations:[[LeagueMatchupDuration]] @@ -61,15 +61,15 @@ struct AssignmentState: Sendable, ~Copyable { var availableSlots:Set var playsAt:PlaysAt - var playsAtTimes:PlaysAtTimes - var playsAtLocations:PlaysAtLocations + var playsAtTimes:ContiguousArray + var playsAtLocations:ContiguousArray /// Available matchups that can be scheduled. var matchups:Set var shuffleHistory = [LeagueShuffleAction]() - func copyable() -> AssignmentStateCopyable { + func copyable() -> AssignmentStateCopyable { return .init( entries: entries, startingTimes: startingTimes, @@ -133,8 +133,8 @@ struct AssignmentState: Sendable, ~Copyable { } // MARK: Copyable -struct AssignmentStateCopyable { - let entries:[LeagueEntry.Runtime] +struct AssignmentStateCopyable { + let entries:[Config.EntryRuntime] let startingTimes:[StaticTime] let matchupDuration:LeagueMatchupDuration let locationTravelDurations:[[LeagueMatchupDuration]] @@ -180,13 +180,13 @@ struct AssignmentStateCopyable { var availableSlots:Set var playsAt:PlaysAt - var playsAtTimes:PlaysAtTimes - var playsAtLocations:PlaysAtLocations + var playsAtTimes:ContiguousArray + var playsAtLocations:ContiguousArray var matchups:Set var shuffleHistory:[LeagueShuffleAction] - func noncopyable() -> AssignmentState { + func noncopyable() -> AssignmentState { return .init( entries: entries, startingTimes: startingTimes, diff --git a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift new file mode 100644 index 0000000..0d15cee --- /dev/null +++ b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift @@ -0,0 +1,11 @@ + +protocol ScheduleConfiguration: Sendable, ~Copyable { + associatedtype TimeSet:SetOfTimeIndexes + associatedtype LocationSet:SetOfLocationIndexes + + typealias EntryRuntime = LeagueEntry.Runtime +} + +struct ScheduleConfig: ScheduleConfiguration { + let entries:[EntryRuntime] +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index 4bf5ede..3263fbb 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -188,7 +188,7 @@ extension LeagueRequestPayload { divisions: divisions, entries: entries, correctMaximumPlayableMatchups: correctMaximumPlayableMatchups, - general: LeagueGeneralSettings.Runtime( + general: LeagueGeneralSettings.Runtime>( gameGap: defaultGameGap, timeSlots: settings.timeSlots, startingTimes: settings.startingTimes.times, @@ -211,11 +211,11 @@ extension LeagueRequestPayload { } extension LeagueRequestPayload { - private func generate( + private func generate( divisions: [LeagueDivision.Runtime], - entries: [LeagueEntry.Runtime], + entries: [Config.EntryRuntime], correctMaximumPlayableMatchups: [UInt32], - general: T + general: LeagueGeneralSettings.Runtime ) async throws(LeagueError) -> LeagueGenerationResult { let daySettings = try parseDaySettings( general: general, @@ -244,7 +244,10 @@ extension LeagueRequestPayload { // MARK: Load division defaults extension LeagueRequestPayload { - private func loadDivisionDefaults( + private func loadDivisionDefaults< + Times: SetOfTimeIndexes, + Locations: SetOfLocationIndexes + >( divisionsCount: Int ) -> DivisionDefaults { var gameDays = [Set]() @@ -352,8 +355,8 @@ extension LeagueRequestPayload { teams: [LeagueEntry], teamsForDivision: inout [Int], divisionDefaults: borrowing DivisionDefaults - ) throws(LeagueError) -> [LeagueEntry.Runtime] { - var entries = [LeagueEntry.Runtime]() + ) throws(LeagueError) -> [LeagueEntry.Runtime] { + var entries = [LeagueEntry.Runtime]() entries.reserveCapacity(teams.count) for (i, team) in teams.enumerated() { if team.hasGameDayTimes { @@ -372,17 +375,14 @@ extension LeagueRequestPayload { } } let division = min(team.division, UInt32(divisionsCount - 1)) - // TODO: fix - let defaultGameTimes = divisionDefaults.gameTimes[unchecked: division].map { BitSet64.init($0) } - let defaultGameLocations = divisionDefaults.gameLocations[unchecked: division].map { BitSet64.init($0) } teamsForDivision[Int(division)] += 1 entries.append(team.runtime( id: LeagueEntry.IDValue(i), division: division, defaultGameDays: divisionDefaults.gameDays[unchecked: division], defaultByes: divisionDefaults.byes[unchecked: division], - defaultGameTimes: defaultGameTimes, - defaultGameLocations: defaultGameLocations + defaultGameTimes: divisionDefaults.gameTimes[unchecked: division], + defaultGameLocations: divisionDefaults.gameLocations[unchecked: division] )) } return entries @@ -451,12 +451,12 @@ extension LeagueRequestPayload { // MARK: Parse day settings extension LeagueRequestPayload { - private func parseDaySettings( - general: T, + private func parseDaySettings( + general: LeagueGeneralSettings.Runtime, correctMaximumPlayableMatchups: [UInt32], - entries: [LeagueEntry.Runtime] - ) throws(LeagueError) -> [T] { - var daySettings = [T]() + entries: [Config.EntryRuntime] + ) throws(LeagueError) -> [LeagueGeneralSettings.Runtime] { + var daySettings = [LeagueGeneralSettings.Runtime]() daySettings.reserveCapacity(gameDays) if hasIndividualDaySettings { for dayIndex in 0..( id: IDValue, division: LeagueDivision.IDValue, defaultGameDays: Set, defaultByes: Set, - defaultGameTimes: [BitSet64], - defaultGameLocations: [BitSet64] - ) -> Runtime { + defaultGameTimes: [Times], + defaultGameLocations: [Locations] + ) -> Runtime { return .init( id: id, division: division, @@ -19,8 +19,7 @@ extension LeagueEntry { ) } - /// For optimal runtime performance. - struct Runtime: Sendable { + struct Runtime: Sendable { /// ID associated with this entry. let id:LeagueEntry.IDValue @@ -33,15 +32,15 @@ extension LeagueEntry { /// Times this entry can play at for a specific day index. /// /// - Usage: [`LeagueDayIndex`: `Set`] - let gameTimes:[BitSet64] + let gameTimes:[TimeSet] /// Locations this entry can play at for a specific day index. /// /// - Usage: [`LeagueDayIndex`: `Set`] - let gameLocations:[BitSet64] + let gameLocations:[LocationSet] /// Home locations for this entry. - let homeLocations:BitSet64 + let homeLocations:LocationSet /// Day indexes where this entry doesn't play due to being on a bye week. let byes:Set @@ -54,8 +53,8 @@ extension LeagueEntry { protobuf: LeagueEntry, defaultGameDays: Set, defaultByes: Set, - defaultGameTimes: [BitSet64], - defaultGameLocations: [BitSet64] + defaultGameTimes: [TimeSet], + defaultGameLocations: [LocationSet] ) { self.id = id self.division = division @@ -71,9 +70,9 @@ extension LeagueEntry { id: LeagueEntry.IDValue, division: LeagueDivision.IDValue, gameDays: Set, - gameTimes: [BitSet64], - gameLocations: [BitSet64], - homeLocations: BitSet64, + gameTimes: [TimeSet], + gameLocations: [LocationSet], + homeLocations: LocationSet, byes: Set, matchupsPerGameDay: LitLeagues_Leagues_EntryMatchupsPerGameDay? ) { diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index 44216dd..0763fb6 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -2,10 +2,7 @@ import StaticDateTimes extension LeagueGeneralSettings { - struct Runtime< - Times: SetOfTimeIndexes, - Locations: SetOfLocationIndexes - >: RuntimeProtocol { + struct Runtime: Sendable { var gameGap:GameGap var timeSlots:LeagueTimeIndex var startingTimes:[StaticTime] @@ -14,12 +11,12 @@ extension LeagueGeneralSettings { var defaultMaxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay var maximumPlayableMatchups:[UInt32] var matchupDuration:LeagueMatchupDuration - var locationTimeExclusivities:[Times]? + var locationTimeExclusivities:[Config.TimeSet]? var locationTravelDurations:[[LeagueMatchupDuration]]? var balanceTimeStrictness:LeagueBalanceStrictness - var balancedTimes:Times + var balancedTimes:Config.TimeSet var balanceLocationStrictness:LeagueBalanceStrictness - var balancedLocations:Locations + var balancedLocations:Config.LocationSet var redistributionSettings:LitLeagues_Leagues_RedistributionSettings? var flags:UInt32 } @@ -99,7 +96,7 @@ extension LeagueGeneralSettings.Runtime { self.matchupDuration = customDaySettings.matchupDuration } if customDaySettings.hasLocationTimeExclusivities { - self.locationTimeExclusivities = customDaySettings.locationTimeExclusivities.locations.map({ Times($0.times) }) + self.locationTimeExclusivities = customDaySettings.locationTimeExclusivities.locations.map({ Config.TimeSet($0.times) }) } if customDaySettings.hasLocationTravelDurations { self.locationTravelDurations = customDaySettings.locationTravelDurations.locations.map({ $0.travelDurationTo }) @@ -166,12 +163,12 @@ extension LeagueGeneralSettings.Runtime { entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, maximumPlayableMatchups: [UInt32], matchupDuration: LeagueMatchupDuration, - locationTimeExclusivities: [Times]?, + locationTimeExclusivities: [Config.TimeSet]?, locationTravelDurations: [[LeagueMatchupDuration]]?, balanceTimeStrictness: LeagueBalanceStrictness, - balancedTimes: Times, + balancedTimes: Config.TimeSet, balanceLocationStrictness: LeagueBalanceStrictness, - balancedLocations: Locations, + balancedLocations: Config.LocationSet, redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, flags: UInt32 ) { diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift index e37dec2..7620abe 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift @@ -1,64 +1,8 @@ import StaticDateTimes -extension LeagueGeneralSettings { - protocol RuntimeProtocol: Sendable, ~Copyable { - associatedtype Times:SetOfTimeIndexes - associatedtype Locations:SetOfLocationIndexes - - var gameGap: GameGap { get set } - var timeSlots: LeagueTimeIndex { get set } - - var startingTimes: [StaticTime] { get set } - var entriesPerLocation: LeagueEntriesPerMatchup { get set } - var locations: LeagueLocationIndex { get set } - var defaultMaxEntryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay { get set } - var maximumPlayableMatchups: [UInt32] { get set } - var matchupDuration: LeagueMatchupDuration { get set } - var locationTimeExclusivities: [Times]? { get set } - var locationTravelDurations: [[LeagueMatchupDuration]]? { get set } - var balanceTimeStrictness: LeagueBalanceStrictness { get set } - var balancedTimes: Times { get set } - var balanceLocationStrictness: LeagueBalanceStrictness { get set } - var balancedLocations: Locations { get set } - var redistributionSettings: LitLeagues_Leagues_RedistributionSettings? { get set } - var flags: UInt32 { get set } - - init(protobuf: LeagueGeneralSettings) throws(LeagueError) - init(gameGap: GameGap, protobuf: LeagueGeneralSettings) - init( - gameGap: GameGap, - timeSlots: LeagueTimeIndex, - startingTimes: [StaticTime], - entriesPerLocation: LeagueEntriesPerMatchup, - locations: LeagueLocationIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - maximumPlayableMatchups: [UInt32], - matchupDuration: LeagueMatchupDuration, - locationTimeExclusivities: [Times]?, - locationTravelDurations: [[LeagueMatchupDuration]]?, - balanceTimeStrictness: LeagueBalanceStrictness, - balancedTimes: Times, - balanceLocationStrictness: LeagueBalanceStrictness, - balancedLocations: Locations, - redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, - flags: UInt32 - ) - - mutating func apply( - gameDays: LeagueDayIndex, - entriesCount: Int, - correctMaximumPlayableMatchups: [UInt32], - general: borrowing Self, - customDaySettings: LeagueGeneralSettings - ) - - func availableSlots() -> Set - } -} - // MARK: Flags -extension LeagueGeneralSettings.RuntimeProtocol { +extension LeagueGeneralSettings.Runtime { func isFlag(_ flag: LeagueSettingFlags) -> Bool { flags & UInt32(1 << flag.rawValue) != 0 } @@ -85,7 +29,7 @@ extension LeagueGeneralSettings.RuntimeProtocol { } // MARK: Compute settings -extension LeagueGeneralSettings.RuntimeProtocol { +extension LeagueGeneralSettings.Runtime { init( protobuf: LeagueGeneralSettings ) throws(LeagueError) { @@ -98,7 +42,7 @@ extension LeagueGeneralSettings.RuntimeProtocol { /// Modifies `timeSlots` and `startingTimes` taking into account current settings. mutating func computeSettings( day: LeagueDayIndex, - entries: [LeagueEntry.Runtime] + entries: [Config.EntryRuntime] ) { if optimizeTimes { var maxMatchupsPlayedToday:LeagueLocationIndex = 0 diff --git a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift index d03e73d..44dbd7d 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift @@ -9,29 +9,8 @@ import SwiftProtobuf // MARK: Runtime extension LeagueRequestPayload { - protocol RuntimeProtocol: Sendable, ~Copyable { - associatedtype ConcreteGeneralSettings:LeagueGeneralSettings.RuntimeProtocol - - /// Number of days where games are played. - var gameDays: LeagueDayIndex { get } - - /// Divisions associated with this schedule. - var divisions: [LeagueDivision.Runtime] { get } - - /// Entries that participate in this schedule. - var entries: [LeagueEntry.Runtime] { get } - - /// General settings for this schedule. - var general: ConcreteGeneralSettings { get } - - /// Individual settings for the given day index. - /// - /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] - var daySettings: [ConcreteGeneralSettings] { get } - } - /// For optimal runtime performance. - struct Runtime: RuntimeProtocol { + struct Runtime: Sendable { /// Number of days where games are played. let gameDays:LeagueDayIndex @@ -39,22 +18,22 @@ extension LeagueRequestPayload { let divisions:[LeagueDivision.Runtime] /// Entries that participate in this schedule. - let entries:[LeagueEntry.Runtime] + let entries:[Config.EntryRuntime] /// General settings for this schedule. - let general:T + let general:LeagueGeneralSettings.Runtime /// Individual settings for the given day index. /// /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] - let daySettings:[T] + let daySettings:[LeagueGeneralSettings.Runtime] init( gameDays: LeagueDayIndex, divisions: [LeagueDivision.Runtime], - entries: [LeagueEntry.Runtime], - general: T, - daySettings: [T] + entries: [Config.EntryRuntime], + general: LeagueGeneralSettings.Runtime, + daySettings: [LeagueGeneralSettings.Runtime] ) { self.gameDays = gameDays self.divisions = divisions diff --git a/Tests/LeagueSchedulingTests/DivisionMatchupCombinationTests.swift b/Tests/LeagueSchedulingTests/DivisionMatchupCombinationTests.swift index a5eb464..5bed22b 100644 --- a/Tests/LeagueSchedulingTests/DivisionMatchupCombinationTests.swift +++ b/Tests/LeagueSchedulingTests/DivisionMatchupCombinationTests.swift @@ -7,14 +7,14 @@ import Testing @Suite struct DivisionMatchupCombinationTests { @Test(.timeLimit(.minutes(1))) - func allDivisionMatchupCombinations() { + func testAllDivisionMatchupCombinations() { var expected:ContiguousArray>> = [ [ [0, 6], [2, 4], [3, 3], [4, 2], [6, 0] ] ] expected += expected - var combos = LeagueScheduleData.allDivisionMatchupCombinations( + var combos = allDivisionMatchupCombinations( entriesPerMatchup: 2, locations: 6, entryCountsForDivision: [12, 12] @@ -29,7 +29,7 @@ struct DivisionMatchupCombinationTests { [0, 5], [2, 3], [3, 2], [5, 0] ] ] - combos = LeagueScheduleData.allDivisionMatchupCombinations( + combos = allDivisionMatchupCombinations( entriesPerMatchup: 2, locations: 6, entryCountsForDivision: [14, 10] @@ -45,7 +45,7 @@ struct DivisionMatchupCombinationTests { [0, 5], [2, 3], [3, 2], [5, 0] ] ] - combos = LeagueScheduleData.allDivisionMatchupCombinations( + combos = allDivisionMatchupCombinations( entriesPerMatchup: 2, locations: 6, entryCountsForDivision: [14, 0, 10] @@ -57,7 +57,7 @@ struct DivisionMatchupCombinationTests { // MARK: Allowed extension DivisionMatchupCombinationTests { @Test(.timeLimit(.minutes(1))) - func allowedDivisionMatchupCombinations() { + func testAllowedDivisionMatchupCombinations() { var expected:ContiguousArray>> = [ [ [0, 6], [6, 0] @@ -75,7 +75,7 @@ extension DivisionMatchupCombinationTests { [6, 0], [0, 6] ] ] - var combos = LeagueScheduleData.allowedDivisionMatchupCombinations( + var combos = allowedDivisionMatchupCombinations( entriesPerMatchup: 2, locations: 6, entryCountsForDivision: [12, 12] @@ -90,7 +90,7 @@ extension DivisionMatchupCombinationTests { [4, 3], [2, 3] ] ] - combos = LeagueScheduleData.allowedDivisionMatchupCombinations( + combos = allowedDivisionMatchupCombinations( entriesPerMatchup: 2, locations: 6, entryCountsForDivision: [14, 10] diff --git a/Tests/LeagueSchedulingTests/League3Tests.swift b/Tests/LeagueSchedulingTests/League3Tests.swift index 918ef8c..04056c5 100644 --- a/Tests/LeagueSchedulingTests/League3Tests.swift +++ b/Tests/LeagueSchedulingTests/League3Tests.swift @@ -21,7 +21,7 @@ struct League3Tests: ScheduleExpectations { return (throughput, UInt64(failed.reduce(0) { $0 + $1.value })) } - //@Test(.timeLimit(.minutes(1))) + @Test(.timeLimit(.minutes(1))) func testthroughput() async throws { try await withThrowingTaskGroup(of: (success: UInt64, fail: UInt64).self) { group in for _ in 0..<5 { diff --git a/Tests/LeagueSchedulingTests/MatchupBlockTests.swift b/Tests/LeagueSchedulingTests/MatchupBlockTests.swift index 277f3dc..2de4287 100644 --- a/Tests/LeagueSchedulingTests/MatchupBlockTests.swift +++ b/Tests/LeagueSchedulingTests/MatchupBlockTests.swift @@ -11,36 +11,36 @@ struct MatchupBlockTests: ScheduleExpectations { extension MatchupBlockTests { @Test(.timeLimit(.minutes(1))) func adjacentTimes() { - var adjacent = LeagueScheduleData.adjacentTimes(for: 0, entryMatchupsPerGameDay: 2) + var adjacent:BitSet64 = calculateAdjacentTimes(for: 0, entryMatchupsPerGameDay: 2) #expect(adjacent == .init([1])) - adjacent = LeagueScheduleData.adjacentTimes(for: 0, entryMatchupsPerGameDay: 3) + adjacent = calculateAdjacentTimes(for: 0, entryMatchupsPerGameDay: 3) #expect(adjacent == .init([1, 2])) - adjacent = LeagueScheduleData.adjacentTimes(for: 0, entryMatchupsPerGameDay: 4) + adjacent = calculateAdjacentTimes(for: 0, entryMatchupsPerGameDay: 4) #expect(adjacent == .init([1, 2, 3])) - adjacent = LeagueScheduleData.adjacentTimes(for: 1, entryMatchupsPerGameDay: 2) + adjacent = calculateAdjacentTimes(for: 1, entryMatchupsPerGameDay: 2) #expect(adjacent == .init([0])) - adjacent = LeagueScheduleData.adjacentTimes(for: 1, entryMatchupsPerGameDay: 3) + adjacent = calculateAdjacentTimes(for: 1, entryMatchupsPerGameDay: 3) #expect(adjacent == .init([0, 2])) - adjacent = LeagueScheduleData.adjacentTimes(for: 1, entryMatchupsPerGameDay: 4) + adjacent = calculateAdjacentTimes(for: 1, entryMatchupsPerGameDay: 4) #expect(adjacent == .init([0, 2, 3])) - adjacent = LeagueScheduleData.adjacentTimes(for: 2, entryMatchupsPerGameDay: 2) + adjacent = calculateAdjacentTimes(for: 2, entryMatchupsPerGameDay: 2) #expect(adjacent == .init([3])) - adjacent = LeagueScheduleData.adjacentTimes(for: 2, entryMatchupsPerGameDay: 3) + adjacent = calculateAdjacentTimes(for: 2, entryMatchupsPerGameDay: 3) #expect(adjacent == .init([0, 1])) - adjacent = LeagueScheduleData.adjacentTimes(for: 2, entryMatchupsPerGameDay: 4) + adjacent = calculateAdjacentTimes(for: 2, entryMatchupsPerGameDay: 4) #expect(adjacent == .init([0, 1, 3])) - adjacent = LeagueScheduleData.adjacentTimes(for: 2, entryMatchupsPerGameDay: 5) + adjacent = calculateAdjacentTimes(for: 2, entryMatchupsPerGameDay: 5) #expect(adjacent == .init([0, 1, 3, 4])) } } diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift index 919c745..a50bac3 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift @@ -57,7 +57,7 @@ extension ScheduleBack2Back { let data = await LeagueSchedule.generate(schedule) try expectations(settings: schedule, matchupsCount: 264, data: data) } - static func scheduleB2B_11GameDays4Times6Locations2Divisions24Teams14_10() throws -> some LeagueRequestPayload.RuntimeProtocol { + static func scheduleB2B_11GameDays4Times6Locations2Divisions24Teams14_10() throws -> UnitTestRuntimeSchedule { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 6, 24) var entryDivisions = [LeagueDivision.IDValue]() diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift index 4d2e938..9b87c47 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift @@ -16,7 +16,7 @@ struct ScheduleBeanBagToss: ScheduleTestsProtocol { data: data ) } - static func schedule8GameDays3Times3Locations1Division9Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { + static func schedule8GameDays3Times3Locations1Division9Teams() throws -> UnitTestRuntimeSchedule { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 3, 3, 9) let entries = getEntries( diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift index b3a8df0..798ac23 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift @@ -198,7 +198,7 @@ extension ScheduleMisc { ) } - static func schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() throws -> some LeagueRequestPayload.RuntimeProtocol { + static func schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() throws -> UnitTestRuntimeSchedule { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 5, 20) var entryDivisions = [LeagueDivision.IDValue]() diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift index 8e50287..7cdedaa 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift @@ -16,7 +16,7 @@ struct ScheduleSameLocationIfB2B: ScheduleTestsProtocol { data: data ) } - static func scheduleSameLocationIfB2B_8GameDays3Times3Locations1Division9Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { + static func scheduleSameLocationIfB2B_8GameDays3Times3Locations1Division9Teams() throws -> UnitTestRuntimeSchedule { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 3, 3, 9) let entries = getEntries( @@ -61,7 +61,7 @@ extension ScheduleSameLocationIfB2B { data: data ) } - static func scheduleSameLocationIfB2B_12GameDays3Times1Locations1Division5Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { + static func scheduleSameLocationIfB2B_12GameDays3Times1Locations1Division5Teams() throws -> UnitTestRuntimeSchedule { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 3, 1, 5) let entries = getEntries( @@ -107,7 +107,7 @@ extension ScheduleSameLocationIfB2B { data: data ) } - static func scheduleSameLocationIfB2B_10GameDays4Times4Locations1Division16Teams() throws -> some LeagueRequestPayload.RuntimeProtocol { + static func scheduleSameLocationIfB2B_10GameDays4Times4Locations1Division16Teams() throws -> UnitTestRuntimeSchedule { let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 4, 16) let entries = getEntries( diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift index 121f8fa..9357009 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift @@ -2,13 +2,13 @@ @testable import LeagueScheduling import Testing -struct BalanceHomeAwayExpectations: ScheduleTestsProtocol { +struct BalanceHomeAwayExpectations: ScheduleTestsProtocol { func expectations( cap: LeagueMaximumSameOpponentMatchupsCap, matchupsPlayedPerDay: ContiguousArray>, assignedEntryHomeAways: AssignedEntryHomeAways, entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionEntries: [LeagueEntry.Runtime] + divisionEntries: [Config.EntryRuntime] ) { for entry in divisionEntries { let entryMatchupsPlayed = matchupsPlayedPerDay.reduce(0, { $0 + $1[unchecked: entry.id] }) @@ -40,7 +40,7 @@ struct BalanceHomeAwayExpectations: ScheduleTestsProtocol { } } func isBalanced( - entry: LeagueEntry.Runtime, + entry: Config.EntryRuntime, matchupsPlayedPerDay: ContiguousArray>, assignedEntryHomeAways: AssignedEntryHomeAways, entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift index 2efa5e2..d111e7c 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift @@ -2,10 +2,10 @@ @testable import LeagueScheduling import Testing -struct DayExpectations: ScheduleTestsProtocol { +struct DayExpectations: ScheduleTestsProtocol { let b2bMatchupsAtDifferentLocations:Set - func expectations(_ settings: some LeagueGeneralSettings.RuntimeProtocol) { + func expectations(_ settings: LeagueGeneralSettings.Runtime) { if settings.sameLocationIfB2B { sameLocationIfB2B() } diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift index 083f8b8..bf3d7b8 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift @@ -2,7 +2,7 @@ @testable import LeagueScheduling import Testing -struct DivisionEntryExpectations: ScheduleTestsProtocol { +struct DivisionEntryExpectations: ScheduleTestsProtocol { let cap:LeagueMaximumSameOpponentMatchupsCap let matchupsPlayedPerDay:ContiguousArray> let assignedEntryHomeAways:AssignedEntryHomeAways @@ -10,10 +10,10 @@ struct DivisionEntryExpectations: ScheduleTestsProtocol { func expectations( balanceHomeAway: Bool, - divisionEntries: [LeagueEntry.Runtime] + divisionEntries: [Config.EntryRuntime] ) { if balanceHomeAway { - BalanceHomeAwayExpectations().expectations( + BalanceHomeAwayExpectations().expectations( cap: cap, matchupsPlayedPerDay: matchupsPlayedPerDay, assignedEntryHomeAways: assignedEntryHomeAways, diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift index 887bf6e..c953e4b 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift @@ -7,8 +7,8 @@ protocol ScheduleExpectations: Sendable { // MARK: Expectations extension ScheduleExpectations { - func expectations( - settings: some LeagueRequestPayload.RuntimeProtocol, + func expectations( + settings: LeagueRequestPayload.Runtime, matchupsCount: Int, data: LeagueGenerationResult ) throws { @@ -92,7 +92,7 @@ extension ScheduleExpectations { } } let settings = settings.daySettings[dayIndex] - let dayExpectations = DayExpectations( + let dayExpectations = DayExpectations( b2bMatchupsAtDifferentLocations: b2bMatchupsAtDifferentLocations ) dayExpectations.expectations(settings) @@ -104,7 +104,7 @@ extension ScheduleExpectations { for (divisionIndex, division) in settings.divisions.enumerated() { let cap = division.maxSameOpponentMatchups let divisionEntries = settings.entries.filter { $0.division == divisionIndex } - let divisionEntryExpectations = DivisionEntryExpectations( + let divisionEntryExpectations = DivisionEntryExpectations( cap: cap, matchupsPlayedPerDay: matchupsPlayedPerDay, assignedEntryHomeAways: assignedEntryHomeAways, @@ -213,7 +213,7 @@ extension ScheduleExpectations { extension ScheduleExpectations { private func allocatedLessThanOrEqualToBalanceTimeNumber( assignedTimes: LeagueAssignedTimes, - balancedTimes: some SetOfTimeIndexes, + balancedTimes: borrowing some SetOfTimeIndexes & ~Copyable, balanceTimeNumber: LeagueTimeIndex ) { for (entryID, assignedTimes) in assignedTimes.enumerated() { @@ -229,7 +229,7 @@ extension ScheduleExpectations { extension ScheduleExpectations { private func allocatedLessThanOrEqualToBalanceLocationNumber( assignedLocations: LeagueAssignedLocations, - balancedLocations: some SetOfLocationIndexes, + balancedLocations: borrowing some SetOfLocationIndexes & ~Copyable, balanceLocationNumber: LeagueLocationIndex ) { for (entryID, assignedLocations) in assignedLocations.enumerated() { diff --git a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift index ca2029f..361344f 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift @@ -4,6 +4,7 @@ import struct FoundationEssentials.Date import StaticDateTimes protocol ScheduleTestsProtocol: ScheduleExpectations { + typealias UnitTestRuntimeSchedule = LeagueRequestPayload.Runtime, BitSet64>> } // MARK: Get entries @@ -16,11 +17,11 @@ extension ScheduleTestsProtocol { teams: Int, homeLocations: ContiguousArray> = [], byes: ContiguousArray> = [] - ) -> [LeagueEntry.Runtime] { + ) -> [ScheduleConfig, BitSet64>.EntryRuntime] { let playsOn = Array(repeating: Set(0.., BitSet64>.EntryRuntime]() entries.reserveCapacity(teams) for division in divisions { let entry = LeagueEntry.Runtime( @@ -85,8 +86,8 @@ extension ScheduleTestsProtocol { divisions: [LeagueDivision.Runtime], divisionsCanPlayOnSameDay: Bool = true, divisionsCanPlayAtSameTime: Bool = true, - entries: [LeagueEntry.Runtime] - ) -> some LeagueRequestPayload.RuntimeProtocol { + entries: [ScheduleConfig, BitSet64>.EntryRuntime] + ) -> LeagueRequestPayload.Runtime, BitSet64>> { let correctMaximumPlayableMatchups = LeagueRequestPayload.calculateMaximumPlayableMatchups( gameDays: gameDays, entryMatchupsPerGameDay: entryMatchupsPerGameDay, @@ -94,9 +95,9 @@ extension ScheduleTestsProtocol { maximumPlayableMatchups: maximumPlayableMatchups ) let times:LeagueTimeIndex = LeagueTimeIndex(startingTimes.count) - let timeSlots:Set = Set(0.. = Set(0.. = .init(0.. = .init(0.., BitSet64>>.init( gameGap: gameGaps, timeSlots: LeagueTimeIndex(startingTimes.count), startingTimes: startingTimes, @@ -121,7 +122,7 @@ extension ScheduleTestsProtocol { ) ) - var daySettings = [LeagueGeneralSettings.Runtime, Set>]() + var daySettings = [LeagueGeneralSettings.Runtime, BitSet64>>]() daySettings.reserveCapacity(gameDays) for day in 0.. Date: Sat, 7 Mar 2026 02:15:04 -0600 Subject: [PATCH 07/33] remove `PlaysAtTimes` and `PlaysAtLocations` typealiases --- Sources/league-scheduling/Leagues.swift | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Sources/league-scheduling/Leagues.swift b/Sources/league-scheduling/Leagues.swift index d78c33d..7d052f3 100644 --- a/Sources/league-scheduling/Leagues.swift +++ b/Sources/league-scheduling/Leagues.swift @@ -77,16 +77,6 @@ typealias MaximumTimeAllocations = ContiguousArray> -/// Times where an entry has already played at for the `day`. -/// -/// - Usage: [`LeagueEntry.IDValue`: `Set`] -typealias PlaysAtTimes = ContiguousArray> - -/// Locations where an entry has already played at for the `day`. -/// -/// - Usage: [`LeagueEntry.IDValue`: `Set`] -typealias PlaysAtLocations = ContiguousArray> - /// Slots where an entry has already played at for the `day`. /// /// - Usage: [`LeagueEntry.IDValue`: `Set`] From fd1bf1316dfef9416890737209378a424e2291c4 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Sat, 7 Mar 2026 06:26:55 -0600 Subject: [PATCH 08/33] finally got the bit sets to perform properly --- Package.swift | 4 +++- .../league-scheduling/LeagueSchedule.swift | 22 ++++++++++++++----- .../data/AssignMatchup.swift | 6 ++--- .../data/BalanceHomeAway.swift | 2 ++ .../data/LeagueScheduleData.swift | 10 ++++++++- .../data/LeagueScheduleDataSnapshot.swift | 6 ++++- .../league-scheduling/data/Redistribute.swift | 6 ++--- .../data/RedistributionData.swift | 2 ++ .../assignmentState/AssignmentState.swift | 4 ++++ .../ScheduleConfiguration.swift | 3 +-- .../data/selectSlot/SelectSlotB2B.swift | 18 +++++++-------- .../selectSlot/SelectSlotEarliestTime.swift | 8 +++---- ...SlotEarliestTimeAndSameLocationIfB2B.swift | 21 ++++++++++-------- .../data/selectSlot/SelectSlotNormal.swift | 8 +++---- .../data/selectSlot/SelectSlotProtocol.swift | 8 +++---- .../LeagueRequestPayload+Generate.swift | 4 ++++ .../LeagueGeneralSettings+Runtime.swift | 2 ++ .../LeagueRequestPayload+Runtime.swift | 12 +++++++++- .../league-scheduling/util/BitSet128.swift | 13 +---------- Sources/league-scheduling/util/BitSet64.swift | 13 +---------- .../expectations/ScheduleExpectations.swift | 2 +- 21 files changed, 97 insertions(+), 77 deletions(-) diff --git a/Package.swift b/Package.swift index 29a2ed3..385ae95 100644 --- a/Package.swift +++ b/Package.swift @@ -30,7 +30,9 @@ let package = Package( .product(name: "SwiftProtobuf", package: "swift-protobuf") ], path: "Sources/league-scheduling", - swiftSettings: [.enableUpcomingFeature("ExistentialAny")] + swiftSettings: [ + .enableUpcomingFeature("ExistentialAny") + ] ), .testTarget( name: "LeagueSchedulingTests", diff --git a/Sources/league-scheduling/LeagueSchedule.swift b/Sources/league-scheduling/LeagueSchedule.swift index 8ab738d..f19aba6 100644 --- a/Sources/league-scheduling/LeagueSchedule.swift +++ b/Sources/league-scheduling/LeagueSchedule.swift @@ -7,8 +7,10 @@ import Foundation // TODO: support divisions on the same day with different times enum LeagueSchedule: Sendable, ~Copyable { + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) static func generate( - _ settings: LeagueRequestPayload.Runtime + _ settings: borrowing LeagueRequestPayload.Runtime ) async -> LeagueGenerationResult { var err:String? = nil var results = [LeagueGenerationData]() @@ -35,8 +37,10 @@ enum LeagueSchedule: Sendable, ~Copyable { // MARK: Generate schedules extension LeagueSchedule { + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) static func generateSchedules( - settings: LeagueRequestPayload.Runtime + settings: borrowing LeagueRequestPayload.Runtime ) async throws -> [LeagueGenerationData] { let divisionsCount = settings.divisions.count var divisionEntries:ContiguousArray> = .init(repeating: Set(), count: divisionsCount) @@ -90,10 +94,11 @@ extension LeagueSchedule { timeout: .seconds(60) ) { group in for (dow, scheduledEntries) in grouped { + let settingsCopy = settings.copy() group.addTask { return Self.generateSchedule( dayOfWeek: dow, - settings: settings, + settings: settingsCopy, dataSnapshot: dataSnapshot, divisionsCount: divisionsCount, maxStartingTimes: finalMaxStartingTimes, @@ -143,9 +148,11 @@ extension LeagueSchedule { // MARK: Generate schedule extension LeagueSchedule { + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) private static func generateSchedule( dayOfWeek: LeagueDayOfWeek, - settings: LeagueRequestPayload.Runtime, + settings: borrowing LeagueRequestPayload.Runtime, dataSnapshot: LeagueScheduleDataSnapshot, divisionsCount: Int, maxStartingTimes: LeagueTimeIndex, @@ -253,6 +260,9 @@ extension LeagueSchedule { finalizeGenerationData(generationData: &generationData, data: data) return generationData } + + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) private static func finalizeGenerationData( generationData: inout LeagueGenerationData, data: borrowing LeagueScheduleData @@ -273,10 +283,12 @@ extension LeagueSchedule { // MARK: Load max allocations extension LeagueSchedule { + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) static func loadMaxAllocations( dataSnapshot: inout LeagueScheduleDataSnapshot, gameDayDivisionEntries: inout ContiguousArray>>, - settings: LeagueRequestPayload.Runtime, + settings: borrowing LeagueRequestPayload.Runtime, maxStartingTimes: LeagueTimeIndex, maxLocations: LeagueLocationIndex, scheduledEntries: Set diff --git a/Sources/league-scheduling/data/AssignMatchup.swift b/Sources/league-scheduling/data/AssignMatchup.swift index 3efe482..cfd5d27 100644 --- a/Sources/league-scheduling/data/AssignMatchup.swift +++ b/Sources/league-scheduling/data/AssignMatchup.swift @@ -48,10 +48,8 @@ extension AssignmentState { team2: pair.team2, assignedTimes: assignedTimes, assignedLocations: assignedLocations, - team1PlaysAtTimes: playsAtTimes[unchecked: pair.team1], - team1PlaysAtLocations: playsAtLocations[unchecked: pair.team1], - team2PlaysAtTimes: playsAtTimes[unchecked: pair.team2], - team2PlaysAtLocations: playsAtLocations[unchecked: pair.team2], + playsAtTimes: playsAtTimes, + playsAtLocations: playsAtLocations, playableSlots: &slots ) #if LOG diff --git a/Sources/league-scheduling/data/BalanceHomeAway.swift b/Sources/league-scheduling/data/BalanceHomeAway.swift index 97fdc3a..292b1fe 100644 --- a/Sources/league-scheduling/data/BalanceHomeAway.swift +++ b/Sources/league-scheduling/data/BalanceHomeAway.swift @@ -1,6 +1,8 @@ extension LeagueMatchupPair { /// Balances home/away allocations, mutating `team1` (home) and `team2` (away) if necessary. + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) mutating func balanceHomeAway( assignmentState: borrowing AssignmentState ) { diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index 2f5a268..18b074e 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -39,6 +39,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { var redistributionData:RedistributionData? var redistributedMatchups = false + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) init( snapshot: LeagueScheduleDataSnapshot ) { @@ -62,6 +64,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { // MARK: Snapshot extension LeagueScheduleData { + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) mutating func loadSnapshot(_ snapshot: LeagueScheduleDataSnapshot) { //locations = snapshot.locations divisionRecurringDayLimitInterval = snapshot.divisionRecurringDayLimitInterval @@ -77,6 +81,8 @@ extension LeagueScheduleData { shuffleHistory = snapshot.shuffleHistory } + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) func snapshot() -> LeagueScheduleDataSnapshot { return .init(self) } @@ -90,12 +96,14 @@ extension LeagueScheduleData { /// - day: Day index that will be scheduled. /// - divisionEntries: Division entries that play on the `day`. (`LeagueDivision.IDValue`: `Set`) /// - entryMatchupsPerGameDay: Number of times a single team will play on `day`. + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) mutating func newDay( day: LeagueDayIndex, daySettings: LeagueGeneralSettings.Runtime, divisionEntries: ContiguousArray>, availableSlots: Set, - settings: LeagueRequestPayload.Runtime, + settings: borrowing LeagueRequestPayload.Runtime, generationData: inout LeagueGenerationData ) throws(LeagueError) { let now = clock.now diff --git a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift index dc0fd88..54afc3c 100644 --- a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift +++ b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift @@ -27,6 +27,8 @@ struct LeagueScheduleDataSnapshot: Sendable { var executionSteps = [ExecutionStep]() var shuffleHistory = [LeagueShuffleAction]() + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) init( maxStartingTimes: LeagueTimeIndex, startingTimes: [StaticTime], @@ -86,7 +88,9 @@ struct LeagueScheduleDataSnapshot: Sendable { shuffleHistory: [] ) } - + + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) init(_ snapshot: borrowing LeagueScheduleData) { entriesPerMatchup = snapshot.entriesPerMatchup entriesCount = snapshot.entriesCount diff --git a/Sources/league-scheduling/data/Redistribute.swift b/Sources/league-scheduling/data/Redistribute.swift index d1931d7..1c516d2 100644 --- a/Sources/league-scheduling/data/Redistribute.swift +++ b/Sources/league-scheduling/data/Redistribute.swift @@ -2,7 +2,7 @@ // MARK: Try redistributing extension LeagueScheduleData { mutating func tryRedistributing( - settings: LeagueRequestPayload.Runtime, + settings: borrowing LeagueRequestPayload.Runtime, generationData: inout LeagueGenerationData ) throws(LeagueError) { guard day > 0 else { @@ -38,7 +38,7 @@ extension LeagueScheduleData { mutating func tryRedistributing( startDayIndex: LeagueDayIndex, - settings: LeagueRequestPayload.Runtime, + settings: borrowing LeagueRequestPayload.Runtime, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable, generationData: inout LeagueGenerationData ) throws(LeagueError) { @@ -46,7 +46,7 @@ extension LeagueScheduleData { redistributionData = .init( dayIndex: day, startDayIndex: startDayIndex, - settings: settings.daySettings[unchecked: day].redistributionSettings ?? settings.general.redistributionSettings, + settings: settings.redistributionSettings(for: day), data: self ) } diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index bbb317a..520b613 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -10,6 +10,8 @@ struct RedistributionData: Sendable { private var redistributedEntries:[UInt16] private(set) var redistributed:Set + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) init( dayIndex: LeagueDayIndex, startDayIndex: LeagueDayIndex, diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift index 529053c..f2302af 100644 --- a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift +++ b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift @@ -69,6 +69,8 @@ struct AssignmentState: Sendable, ~Copyable { var shuffleHistory = [LeagueShuffleAction]() + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) func copyable() -> AssignmentStateCopyable { return .init( entries: entries, @@ -186,6 +188,8 @@ struct AssignmentStateCopyable { var shuffleHistory:[LeagueShuffleAction] + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) func noncopyable() -> AssignmentState { return .init( entries: entries, diff --git a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift index 0d15cee..90e119d 100644 --- a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift +++ b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift @@ -6,6 +6,5 @@ protocol ScheduleConfiguration: Sendable, ~Copyable { typealias EntryRuntime = LeagueEntry.Runtime } -struct ScheduleConfig: ScheduleConfiguration { - let entries:[EntryRuntime] +enum ScheduleConfig: ScheduleConfiguration { } \ No newline at end of file diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift index 3ed0480..dad05f5 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift @@ -2,20 +2,18 @@ struct SelectSlotB2B: SelectSlotProtocol, ~Copyable { let entryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay - func select( + func select( team1: LeagueEntry.IDValue, team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, - team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + playsAtTimes: ContiguousArray, + playsAtLocations: ContiguousArray, playableSlots: inout Set ) -> LeagueAvailableSlot? { filter( - team1PlaysAtTimes: team1PlaysAtTimes, - team2PlaysAtTimes: team2PlaysAtTimes, + team1PlaysAtTimes: playsAtTimes[unchecked: team1], + team2PlaysAtTimes: playsAtTimes[unchecked: team2], playableSlots: &playableSlots ) return SelectSlotNormal.select( @@ -30,9 +28,9 @@ struct SelectSlotB2B: SelectSlotProtocol, ~Copyable { extension SelectSlotB2B { /// Mutates `playableSlots`, if `team1` AND `team2` haven't played already, so it only contains the first slots applicable for a matchup block. - private func filter( - team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, + private func filter( + team1PlaysAtTimes: TimeSet, + team2PlaysAtTimes: TimeSet, playableSlots: inout Set ) { //print("filterSlotBack2Back;playsAtTimes[unchecked: team1].isEmpty=\(playsAtTimes[unchecked: team1].isEmpty);playsAtTimes[unchecked: team2].isEmpty=\(playsAtTimes[unchecked: team2].isEmpty)") diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift index 4b96d8d..bd62b9c 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift @@ -1,14 +1,12 @@ struct SelectSlotEarliestTime: SelectSlotProtocol, ~Copyable { - func select( + func select( team1: LeagueEntry.IDValue, team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, - team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + playsAtTimes: ContiguousArray, + playsAtLocations: ContiguousArray, playableSlots: inout Set ) -> LeagueAvailableSlot? { return Self.select( diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift index 240ab70..46d65af 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift @@ -1,18 +1,18 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable { - func select( + func select( team1: LeagueEntry.IDValue, team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, - team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + playsAtTimes: ContiguousArray, + playsAtLocations: ContiguousArray, playableSlots: inout Set ) -> LeagueAvailableSlot? { guard !playableSlots.isEmpty else { return nil } - guard !(team1PlaysAtTimes.isEmpty || team2PlaysAtTimes.isEmpty) else { + let homePlaysAtTimes = playsAtTimes[unchecked: team1] + let awayPlaysAtTimes = playsAtTimes[unchecked: team1] + guard !(homePlaysAtTimes.isEmpty || awayPlaysAtTimes.isEmpty) else { return SelectSlotEarliestTime.select( team1: team1, team2: team2, @@ -27,6 +27,9 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable let team2Times = assignedTimes[unchecked: team2] let team2Locations = assignedLocations[unchecked: team2] + let team1PlaysAtLocations = playsAtLocations[unchecked: team1] + let team2PlaysAtLocations = playsAtLocations[unchecked: team2] + var nonBackToBackSlots = [LeagueAvailableSlot]() nonBackToBackSlots.reserveCapacity(playableSlots.count) @@ -38,9 +41,9 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable team2Locations: team2Locations, playableSlots: playableSlots ) { - if targetSlot.time > 0 && (team1PlaysAtTimes.contains(targetSlot.time-1) || team2PlaysAtTimes.contains(targetSlot.time-1)) - || team1PlaysAtTimes.contains(targetSlot.time+1) - || team2PlaysAtTimes.contains(targetSlot.time+1) { + if targetSlot.time > 0 && (homePlaysAtTimes.contains(targetSlot.time-1) || awayPlaysAtTimes.contains(targetSlot.time-1)) + || homePlaysAtTimes.contains(targetSlot.time+1) + || awayPlaysAtTimes.contains(targetSlot.time+1) { // is back-to-back if team1PlaysAtLocations.contains(targetSlot.location) || team2PlaysAtLocations.contains(targetSlot.location) { // make them play b2b on the same location diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift index 9efeb72..952c1cf 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift @@ -1,14 +1,12 @@ struct SelectSlotNormal: SelectSlotProtocol, ~Copyable { - func select( + func select( team1: LeagueEntry.IDValue, team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, - team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + playsAtTimes: ContiguousArray, + playsAtLocations: ContiguousArray, playableSlots: inout Set ) -> LeagueAvailableSlot? { return Self.select( diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift index b1fc24e..9f5971d 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift @@ -1,14 +1,12 @@ protocol SelectSlotProtocol: Sendable, ~Copyable { - func select( + func select( team1: LeagueEntry.IDValue, team2: LeagueEntry.IDValue, assignedTimes: LeagueAssignedTimes, assignedLocations: LeagueAssignedLocations, - team1PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team1PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, - team2PlaysAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, - team2PlaysAtLocations: borrowing some SetOfLocationIndexes & ~Copyable, + playsAtTimes: ContiguousArray, + playsAtLocations: ContiguousArray, playableSlots: inout Set ) -> LeagueAvailableSlot? } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index 3263fbb..8fe86ba 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -211,6 +211,8 @@ extension LeagueRequestPayload { } extension LeagueRequestPayload { + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) private func generate( divisions: [LeagueDivision.Runtime], entries: [Config.EntryRuntime], @@ -451,6 +453,8 @@ extension LeagueRequestPayload { // MARK: Parse day settings extension LeagueRequestPayload { + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) private func parseDaySettings( general: LeagueGeneralSettings.Runtime, correctMaximumPlayableMatchups: [UInt32], diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index 0763fb6..f8ffff1 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -154,6 +154,8 @@ extension LeagueGeneralSettings.Runtime { balancedLocations.contains(location) } + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) init( gameGap: GameGap, timeSlots: LeagueTimeIndex, diff --git a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift index 44dbd7d..8dd5e74 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift @@ -10,7 +10,7 @@ import SwiftProtobuf // MARK: Runtime extension LeagueRequestPayload { /// For optimal runtime performance. - struct Runtime: Sendable { + struct Runtime: Sendable, ~Copyable { /// Number of days where games are played. let gameDays:LeagueDayIndex @@ -28,6 +28,8 @@ extension LeagueRequestPayload { /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] let daySettings:[LeagueGeneralSettings.Runtime] + @_specialize(where Config == ScheduleConfig, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set>) init( gameDays: LeagueDayIndex, divisions: [LeagueDivision.Runtime], @@ -41,5 +43,13 @@ extension LeagueRequestPayload { self.general = general self.daySettings = daySettings } + + func copy() -> Self { + .init(gameDays: gameDays, divisions: divisions, entries: entries, general: general, daySettings: daySettings) + } + + func redistributionSettings(for day: LeagueDayIndex) -> LitLeagues_Leagues_RedistributionSettings? { + daySettings[unchecked: day].redistributionSettings ?? general.redistributionSettings + } } } \ No newline at end of file diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index 3f4754b..cee026b 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -18,6 +18,7 @@ struct BitSet128: Sendable { var count: Int { storage.nonzeroBitCount } + var isEmpty: Bool { storage == 0 } @@ -61,18 +62,6 @@ extension BitSet128 { temp &= (temp - 1) } } - - func forEachBitWithReturn(_ yield: (Element) -> Result?) -> Result? { - var temp = storage - while temp != 0 { - let index = temp.trailingZeroBitCount - if let r = yield(Element(index)) { - return r - } - temp &= (temp - 1) - } - return nil - } } // MARK: form union diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index a2bce14..c632f6b 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -24,6 +24,7 @@ struct BitSet64: Sendable { var count: Int { storage.nonzeroBitCount } + var isEmpty: Bool { storage == 0 } @@ -67,18 +68,6 @@ extension BitSet64 { temp &= (temp - 1) } } - - func forEachBitWithReturn(_ yield: (Element) -> Result?) -> Result? { - var temp = storage - while temp != 0 { - let index = temp.trailingZeroBitCount - if let r = yield(Element(index)) { - return r - } - temp &= (temp - 1) - } - return nil - } } // MARK: form union diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift index c953e4b..5667fed 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift @@ -8,7 +8,7 @@ protocol ScheduleExpectations: Sendable { // MARK: Expectations extension ScheduleExpectations { func expectations( - settings: LeagueRequestPayload.Runtime, + settings: borrowing LeagueRequestPayload.Runtime, matchupsCount: Int, data: LeagueGenerationResult ) throws { From c5361f480a789f06ab4d5848ebe5cc017faf780e Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Sat, 7 Mar 2026 07:40:25 -0600 Subject: [PATCH 09/33] use bit sets for `Set` if possible --- .../league-scheduling/LeagueSchedule.swift | 32 +++--- .../data/BalanceHomeAway.swift | 4 +- .../data/LeagueScheduleData.swift | 16 +-- .../data/LeagueScheduleDataSnapshot.swift | 8 +- .../data/RedistributionData.swift | 4 +- .../assignmentState/AssignmentState.swift | 8 +- .../ScheduleConfiguration.swift | 6 +- .../LeagueRequestPayload+Generate.swift | 99 +++++++++++++------ .../runtime/LeagueDivision+Runtime.swift | 16 +-- .../runtime/LeagueEntry+Runtime.swift | 26 ++--- .../LeagueGeneralSettings+Runtime.swift | 4 +- .../LeagueRequestPayload+Runtime.swift | 8 +- .../league-scheduling/util/AbstractSet.swift | 8 +- .../league-scheduling/util/BitSet128.swift | 1 + Sources/league-scheduling/util/BitSet64.swift | 1 + .../BalanceHomeAwayExpectations.swift | 4 +- .../util/ScheduleTestsProtocol.swift | 33 ++++--- 17 files changed, 162 insertions(+), 116 deletions(-) diff --git a/Sources/league-scheduling/LeagueSchedule.swift b/Sources/league-scheduling/LeagueSchedule.swift index f19aba6..18e50d8 100644 --- a/Sources/league-scheduling/LeagueSchedule.swift +++ b/Sources/league-scheduling/LeagueSchedule.swift @@ -7,8 +7,8 @@ import Foundation // TODO: support divisions on the same day with different times enum LeagueSchedule: Sendable, ~Copyable { - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) static func generate( _ settings: borrowing LeagueRequestPayload.Runtime ) async -> LeagueGenerationResult { @@ -37,8 +37,8 @@ enum LeagueSchedule: Sendable, ~Copyable { // MARK: Generate schedules extension LeagueSchedule { - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) static func generateSchedules( settings: borrowing LeagueRequestPayload.Runtime ) async throws -> [LeagueGenerationData] { @@ -62,7 +62,7 @@ extension LeagueSchedule { } } - let maxSameOpponentMatchups = Self.maximumSameOpponentMatchups( + let (maxSameOpponentMatchups, _):(LeagueMaximumSameOpponentMatchups, Config?) = Self.maximumSameOpponentMatchups( gameDays: settings.gameDays, entriesCount: settings.entries.count, divisionEntries: divisionEntries, @@ -148,8 +148,8 @@ extension LeagueSchedule { // MARK: Generate schedule extension LeagueSchedule { - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) private static func generateSchedule( dayOfWeek: LeagueDayOfWeek, settings: borrowing LeagueRequestPayload.Runtime, @@ -261,8 +261,8 @@ extension LeagueSchedule { return generationData } - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) private static func finalizeGenerationData( generationData: inout LeagueGenerationData, data: borrowing LeagueScheduleData @@ -283,8 +283,8 @@ extension LeagueSchedule { // MARK: Load max allocations extension LeagueSchedule { - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) static func loadMaxAllocations( dataSnapshot: inout LeagueScheduleDataSnapshot, gameDayDivisionEntries: inout ContiguousArray>>, @@ -401,12 +401,14 @@ extension LeagueSchedule { // MARK: Maximum same opponent matchups extension LeagueSchedule { - static func maximumSameOpponentMatchups( + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) + static func maximumSameOpponentMatchups( gameDays: LeagueDayIndex, entriesCount: Int, divisionEntries: ContiguousArray>, - divisions: [LeagueDivision.Runtime] - ) -> LeagueMaximumSameOpponentMatchups { + divisions: [Config.DivisionRuntime] + ) -> (LeagueMaximumSameOpponentMatchups, Config?) { var maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchups = .init(repeating: .init(repeating: .max, count: entriesCount), count: entriesCount) for (divisionIndex, division) in divisions.enumerated() { let divisionEntries = divisionEntries[divisionIndex] @@ -417,6 +419,6 @@ extension LeagueSchedule { } } } - return maxSameOpponentMatchups + return (maxSameOpponentMatchups, nil) } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/BalanceHomeAway.swift b/Sources/league-scheduling/data/BalanceHomeAway.swift index 292b1fe..656c690 100644 --- a/Sources/league-scheduling/data/BalanceHomeAway.swift +++ b/Sources/league-scheduling/data/BalanceHomeAway.swift @@ -1,8 +1,8 @@ extension LeagueMatchupPair { /// Balances home/away allocations, mutating `team1` (home) and `team2` (away) if necessary. - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) mutating func balanceHomeAway( assignmentState: borrowing AssignmentState ) { diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index 18b074e..10466d9 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -39,8 +39,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { var redistributionData:RedistributionData? var redistributedMatchups = false - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) init( snapshot: LeagueScheduleDataSnapshot ) { @@ -64,8 +64,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { // MARK: Snapshot extension LeagueScheduleData { - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) mutating func loadSnapshot(_ snapshot: LeagueScheduleDataSnapshot) { //locations = snapshot.locations divisionRecurringDayLimitInterval = snapshot.divisionRecurringDayLimitInterval @@ -81,8 +81,8 @@ extension LeagueScheduleData { shuffleHistory = snapshot.shuffleHistory } - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) func snapshot() -> LeagueScheduleDataSnapshot { return .init(self) } @@ -96,8 +96,8 @@ extension LeagueScheduleData { /// - day: Day index that will be scheduled. /// - divisionEntries: Division entries that play on the `day`. (`LeagueDivision.IDValue`: `Set`) /// - entryMatchupsPerGameDay: Number of times a single team will play on `day`. - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) mutating func newDay( day: LeagueDayIndex, daySettings: LeagueGeneralSettings.Runtime, diff --git a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift index 54afc3c..f162a11 100644 --- a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift +++ b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift @@ -27,8 +27,8 @@ struct LeagueScheduleDataSnapshot: Sendable { var executionSteps = [ExecutionStep]() var shuffleHistory = [LeagueShuffleAction]() - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) init( maxStartingTimes: LeagueTimeIndex, startingTimes: [StaticTime], @@ -89,8 +89,8 @@ struct LeagueScheduleDataSnapshot: Sendable { ) } - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) init(_ snapshot: borrowing LeagueScheduleData) { entriesPerMatchup = snapshot.entriesPerMatchup entriesCount = snapshot.entriesCount diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index 520b613..fdd84f1 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -10,8 +10,8 @@ struct RedistributionData: Sendable { private var redistributedEntries:[UInt16] private(set) var redistributed:Set - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) init( dayIndex: LeagueDayIndex, startDayIndex: LeagueDayIndex, diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift index f2302af..6d54d3a 100644 --- a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift +++ b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift @@ -69,8 +69,8 @@ struct AssignmentState: Sendable, ~Copyable { var shuffleHistory = [LeagueShuffleAction]() - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) func copyable() -> AssignmentStateCopyable { return .init( entries: entries, @@ -188,8 +188,8 @@ struct AssignmentStateCopyable { var shuffleHistory:[LeagueShuffleAction] - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) func noncopyable() -> AssignmentState { return .init( entries: entries, diff --git a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift index 90e119d..1b79403 100644 --- a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift +++ b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift @@ -1,10 +1,12 @@ protocol ScheduleConfiguration: Sendable, ~Copyable { + associatedtype DaySet:SetOfDayIndexes associatedtype TimeSet:SetOfTimeIndexes associatedtype LocationSet:SetOfLocationIndexes - typealias EntryRuntime = LeagueEntry.Runtime + typealias DivisionRuntime = LeagueDivision.Runtime + typealias EntryRuntime = LeagueEntry.Runtime } -enum ScheduleConfig: ScheduleConfiguration { +enum ScheduleConfig: ScheduleConfiguration { } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index 8fe86ba..4b5551a 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -71,6 +71,37 @@ extension LeagueRequestPayload { defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, + d: BitSet64() + ) + case 1...128: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + d: BitSet128() + ) + default: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + d: Set() + ) + } + } + private func generate( + defaultGameGap: GameGap, + divisionsCount: Int, + startingDayOfWeek: LeagueDayOfWeek, + d: DaySet + ) async throws(LeagueError) -> LeagueGenerationResult { + switch settings.timeSlots { + case 1...64: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + d: d, t: BitSet64() ) case 1...128: @@ -78,6 +109,7 @@ extension LeagueRequestPayload { defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, + d: d, t: BitSet128() ) default: @@ -85,14 +117,16 @@ extension LeagueRequestPayload { defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, + d: d, t: Set() ) } } - private func generate( + private func generate( defaultGameGap: GameGap, divisionsCount: Int, startingDayOfWeek: LeagueDayOfWeek, + d: DaySet, t: Times, ) async throws(LeagueError) -> LeagueGenerationResult { switch settings.locations { @@ -101,6 +135,7 @@ extension LeagueRequestPayload { defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, + defaultGameDays: d, defaultTimes: t, defaultLocations: BitSet64() ) @@ -109,6 +144,7 @@ extension LeagueRequestPayload { defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, + defaultGameDays: d, defaultTimes: t, defaultLocations: BitSet128() ) @@ -117,6 +153,7 @@ extension LeagueRequestPayload { defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, + defaultGameDays: d, defaultTimes: t, defaultLocations: Set() ) @@ -125,14 +162,15 @@ extension LeagueRequestPayload { } extension LeagueRequestPayload { - private func generate( + private func generate( defaultGameGap: GameGap, divisionsCount: Int, startingDayOfWeek: LeagueDayOfWeek, + defaultGameDays: DaySet, defaultTimes: Times, defaultLocations: Locations ) async throws(LeagueError) -> LeagueGenerationResult { - let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) + let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) var teamsForDivision = [Int](repeating: 0, count: divisionsCount) let entries = try parseEntries( divisionsCount: divisionsCount, @@ -188,7 +226,7 @@ extension LeagueRequestPayload { divisions: divisions, entries: entries, correctMaximumPlayableMatchups: correctMaximumPlayableMatchups, - general: LeagueGeneralSettings.Runtime>( + general: LeagueGeneralSettings.Runtime>( gameGap: defaultGameGap, timeSlots: settings.timeSlots, startingTimes: settings.startingTimes.times, @@ -211,10 +249,10 @@ extension LeagueRequestPayload { } extension LeagueRequestPayload { - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) private func generate( - divisions: [LeagueDivision.Runtime], + divisions: [Config.DivisionRuntime], entries: [Config.EntryRuntime], correctMaximumPlayableMatchups: [UInt32], general: LeagueGeneralSettings.Runtime @@ -236,9 +274,9 @@ extension LeagueRequestPayload { // MARK: Division defaults extension LeagueRequestPayload { - struct DivisionDefaults: Sendable, ~Copyable { - let gameDays:[Set] - let byes:[Set] + struct DivisionDefaults: Sendable, ~Copyable { + let gameDays:[DaySet] + let byes:[DaySet] let gameTimes:[[Times]] let gameLocations:[[Locations]] } @@ -247,13 +285,14 @@ extension LeagueRequestPayload { // MARK: Load division defaults extension LeagueRequestPayload { private func loadDivisionDefaults< + DaySet: SetOfDayIndexes, Times: SetOfTimeIndexes, Locations: SetOfLocationIndexes >( divisionsCount: Int - ) -> DivisionDefaults { - var gameDays = [Set]() - var byes = [Set]() + ) -> DivisionDefaults { + var gameDays = [DaySet]() + var byes = [DaySet]() var gameTimes = [[Times]]() var gameLocations = [[Locations]]() gameDays.reserveCapacity(divisionsCount) @@ -272,17 +311,17 @@ extension LeagueRequestPayload { } if hasDivisions { for division in divisions.divisions { - let targetGameDays:Set + let targetGameDays:DaySet if division.hasGameDays { - targetGameDays = Set(division.gameDays.gameDayIndexes) + targetGameDays = .init(division.gameDays.gameDayIndexes) } else { - targetGameDays = Set(0..( + private func parseEntries( divisionsCount: Int, teams: [LeagueEntry], teamsForDivision: inout [Int], - divisionDefaults: borrowing DivisionDefaults - ) throws(LeagueError) -> [LeagueEntry.Runtime] { - var entries = [LeagueEntry.Runtime]() + divisionDefaults: borrowing DivisionDefaults + ) throws(LeagueError) -> [LeagueEntry.Runtime] { + var entries = [LeagueEntry.Runtime]() entries.reserveCapacity(teams.count) for (i, team) in teams.enumerated() { if team.hasGameDayTimes { @@ -393,15 +432,15 @@ extension LeagueRequestPayload { // MARK: Parse divisions extension LeagueRequestPayload { - private func parseDivisions( + private func parseDivisions( divisionsCount: Int, locations: LeagueLocationIndex, - divisionGameDays: [Set], + divisionGameDays: [DaySet], defaultGameGap: GameGap, fallbackDayOfWeek: LeagueDayOfWeek, teamsForDivision: [Int] - ) throws(LeagueError) -> [LeagueDivision.Runtime] { - var runtimeDivisions = [LeagueDivision.Runtime]() + ) throws(LeagueError) -> [LeagueDivision.Runtime] { + var runtimeDivisions = [LeagueDivision.Runtime]() runtimeDivisions.reserveCapacity(divisionsCount) if hasDivisions { for (i, division) in divisions.divisions.enumerated() { @@ -453,8 +492,8 @@ extension LeagueRequestPayload { // MARK: Parse day settings extension LeagueRequestPayload { - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) private func parseDaySettings( general: LeagueGeneralSettings.Runtime, correctMaximumPlayableMatchups: [UInt32], diff --git a/Sources/league-scheduling/generated/runtime/LeagueDivision+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueDivision+Runtime.swift index 3933900..2fc8ce5 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueDivision+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueDivision+Runtime.swift @@ -1,11 +1,11 @@ extension LeagueDivision { - func runtime( - defaultGameDays: Set, + func runtime( + defaultGameDays: DaySet, defaultGameGap: GameGap, fallbackDayOfWeek: LeagueDayOfWeek, fallbackMaxSameOpponentMatchups: LeagueMaximumSameOpponentMatchupsCap - ) throws(LeagueError) -> Runtime { + ) throws(LeagueError) -> Runtime { try .init( protobuf: self, defaultGameDays: defaultGameDays, @@ -16,28 +16,28 @@ extension LeagueDivision { } /// For optimal runtime performance. - struct Runtime: Sendable { + struct Runtime: Sendable { let dayOfWeek:LeagueDayOfWeek - let gameDays:Set + let gameDays:DaySet let gameGaps:[GameGap] let maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchupsCap init( protobuf: LeagueDivision, - defaultGameDays: Set, + defaultGameDays: DaySet, defaultGameGap: GameGap, fallbackDayOfWeek: LeagueDayOfWeek, fallbackMaxSameOpponentMatchups: LeagueMaximumSameOpponentMatchupsCap ) throws(LeagueError) { dayOfWeek = protobuf.hasDayOfWeek ? LeagueDayOfWeek(protobuf.dayOfWeek) : fallbackDayOfWeek - self.gameDays = protobuf.hasGameDays ? Set(protobuf.gameDays.gameDayIndexes) : defaultGameDays + self.gameDays = protobuf.hasGameDays ? .init(protobuf.gameDays.gameDayIndexes) : defaultGameDays gameGaps = protobuf.hasGameGaps ? try Self.parseGameGaps(protobuf.gameGaps.gameGaps) : .init(repeating: defaultGameGap, count: defaultGameDays.count) maxSameOpponentMatchups = protobuf.hasMaxSameOpponentMatchups ? protobuf.maxSameOpponentMatchups : fallbackMaxSameOpponentMatchups } init( dayOfWeek: LeagueDayOfWeek, - gameDays: Set, + gameDays: DaySet, gameGaps: [GameGap], maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchupsCap ) { diff --git a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift index 508fb7a..f0920a4 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift @@ -1,13 +1,13 @@ extension LeagueEntry { - func runtime( + func runtime( id: IDValue, division: LeagueDivision.IDValue, - defaultGameDays: Set, - defaultByes: Set, + defaultGameDays: DaySet, + defaultByes: DaySet, defaultGameTimes: [Times], defaultGameLocations: [Locations] - ) -> Runtime { + ) -> Runtime { return .init( id: id, division: division, @@ -19,7 +19,7 @@ extension LeagueEntry { ) } - struct Runtime: Sendable { + struct Runtime: Sendable { /// ID associated with this entry. let id:LeagueEntry.IDValue @@ -27,7 +27,7 @@ extension LeagueEntry { let division:LeagueDivision.IDValue /// Game days this entry can play on. - let gameDays:Set + let gameDays:DaySet /// Times this entry can play at for a specific day index. /// @@ -43,7 +43,7 @@ extension LeagueEntry { let homeLocations:LocationSet /// Day indexes where this entry doesn't play due to being on a bye week. - let byes:Set + let byes:DaySet let matchupsPerGameDay:LitLeagues_Leagues_EntryMatchupsPerGameDay? @@ -51,29 +51,29 @@ extension LeagueEntry { id: LeagueEntry.IDValue, division: LeagueDivision.IDValue, protobuf: LeagueEntry, - defaultGameDays: Set, - defaultByes: Set, + defaultGameDays: DaySet, + defaultByes: DaySet, defaultGameTimes: [TimeSet], defaultGameLocations: [LocationSet] ) { self.id = id self.division = division - gameDays = protobuf.hasGameDays ? Set(protobuf.gameDays.gameDayIndexes) : defaultGameDays + gameDays = protobuf.hasGameDays ? .init(protobuf.gameDays.gameDayIndexes) : defaultGameDays gameTimes = protobuf.hasGameDayTimes ? protobuf.gameDayTimes.times.map({ .init($0.times) }) : defaultGameTimes gameLocations = protobuf.hasGameDayLocations ? protobuf.gameDayLocations.locations.map({ .init($0.locations) }) : defaultGameLocations homeLocations = protobuf.hasHomeLocations ? .init(protobuf.homeLocations.homeLocations) : .init() - byes = protobuf.hasByes ? Set(protobuf.byes.byes) : defaultByes + byes = protobuf.hasByes ? .init(protobuf.byes.byes) : defaultByes matchupsPerGameDay = protobuf.hasMatchupsPerGameDay ? protobuf.matchupsPerGameDay : nil } init( id: LeagueEntry.IDValue, division: LeagueDivision.IDValue, - gameDays: Set, + gameDays: DaySet, gameTimes: [TimeSet], gameLocations: [LocationSet], homeLocations: LocationSet, - byes: Set, + byes: DaySet, matchupsPerGameDay: LitLeagues_Leagues_EntryMatchupsPerGameDay? ) { self.id = id diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index f8ffff1..cde4af9 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -154,8 +154,8 @@ extension LeagueGeneralSettings.Runtime { balancedLocations.contains(location) } - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) init( gameGap: GameGap, timeSlots: LeagueTimeIndex, diff --git a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift index 8dd5e74..946de7d 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift @@ -15,7 +15,7 @@ extension LeagueRequestPayload { let gameDays:LeagueDayIndex /// Divisions associated with this schedule. - let divisions:[LeagueDivision.Runtime] + let divisions:[Config.DivisionRuntime] /// Entries that participate in this schedule. let entries:[Config.EntryRuntime] @@ -28,11 +28,11 @@ extension LeagueRequestPayload { /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] let daySettings:[LeagueGeneralSettings.Runtime] - @_specialize(where Config == ScheduleConfig, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set>) init( gameDays: LeagueDayIndex, - divisions: [LeagueDivision.Runtime], + divisions: [Config.DivisionRuntime], entries: [Config.EntryRuntime], general: LeagueGeneralSettings.Runtime, daySettings: [LeagueGeneralSettings.Runtime] diff --git a/Sources/league-scheduling/util/AbstractSet.swift b/Sources/league-scheduling/util/AbstractSet.swift index 9189d45..b8a3259 100644 --- a/Sources/league-scheduling/util/AbstractSet.swift +++ b/Sources/league-scheduling/util/AbstractSet.swift @@ -23,10 +23,9 @@ protocol AbstractSet: Sendable, ~Copyable { func forEach(_ body: (Element) throws -> Void) rethrows } -protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex { -} -protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LeagueLocationIndex { -} +protocol SetOfDayIndexes: AbstractSet, ~Copyable where Element == LeagueDayIndex {} +protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex {} +protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LeagueLocationIndex {} extension Set: AbstractSet { @inline(__always) @@ -44,5 +43,6 @@ extension Set: AbstractSet { } } +extension Set: SetOfDayIndexes {} extension Set: SetOfTimeIndexes {} extension Set: SetOfLocationIndexes {} \ No newline at end of file diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index cee026b..d4d2cf5 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -86,5 +86,6 @@ extension BitSet128 { // MARK: AbstractSet extension BitSet128: AbstractSet {} +extension BitSet128: SetOfDayIndexes where Element == LeagueTimeIndex {} extension BitSet128: SetOfTimeIndexes where Element == LeagueTimeIndex {} extension BitSet128: SetOfLocationIndexes where Element == LeagueLocationIndex {} \ No newline at end of file diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index c632f6b..e2b0fb5 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -92,5 +92,6 @@ extension BitSet64 { // MARK: AbstractSet extension BitSet64: AbstractSet {} +extension BitSet64: SetOfDayIndexes where Element == LeagueTimeIndex {} extension BitSet64: SetOfTimeIndexes where Element == LeagueTimeIndex {} extension BitSet64: SetOfLocationIndexes where Element == LeagueLocationIndex {} \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift index 9357009..ee627bb 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift @@ -78,11 +78,11 @@ extension LeagueEntry.Runtime { func isEqual(to rhs: Self) -> Bool { id == rhs.id && division == rhs.division - && gameDays == rhs.gameDays + //&& gameDays == rhs.gameDays //&& lhs.gameTimes == rhs.gameTimes //&& lhs.gameLocations == rhs.gameLocations //&& lhs.homeLocations == rhs.homeLocations - && byes == rhs.byes + //&& byes == rhs.byes //&& lhs.matchupsPerGameDay == rhs.matchupsPerGameDay } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift index 361344f..bca161e 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift @@ -4,7 +4,8 @@ import struct FoundationEssentials.Date import StaticDateTimes protocol ScheduleTestsProtocol: ScheduleExpectations { - typealias UnitTestRuntimeSchedule = LeagueRequestPayload.Runtime, BitSet64>> + typealias UnitTestScheduleConfig = ScheduleConfig, BitSet64, BitSet64> + typealias UnitTestRuntimeSchedule = LeagueRequestPayload.Runtime } // MARK: Get entries @@ -15,13 +16,13 @@ extension ScheduleTestsProtocol { times: LeagueTimeIndex, locations: LeagueLocationIndex, teams: Int, - homeLocations: ContiguousArray> = [], - byes: ContiguousArray> = [] - ) -> [ScheduleConfig, BitSet64>.EntryRuntime] { - let playsOn = Array(repeating: Set(0.., BitSet64>.EntryRuntime]() + homeLocations: ContiguousArray = [], + byes: ContiguousArray = [] + ) -> [UnitTestScheduleConfig.EntryRuntime] { + let playsOn = Array(repeating: UnitTestScheduleConfig.DaySet(0.. LeagueDivision.Runtime { + ) throws(LeagueError) -> UnitTestScheduleConfig.DivisionRuntime { let maxSameOpponentMatchups = try LeagueRequestPayload.calculateMaximumSameOpponentMatchupsCap( gameDays: values.gameDays, entryMatchupsPerGameDay: values.entryMatchupsPerGameDay, @@ -57,7 +58,7 @@ extension ScheduleTestsProtocol { ) return .init( dayOfWeek: dayOfWeek, - gameDays: Set(0.., BitSet64>.EntryRuntime] - ) -> LeagueRequestPayload.Runtime, BitSet64>> { + entries: [UnitTestScheduleConfig.EntryRuntime] + ) -> LeagueRequestPayload.Runtime { let correctMaximumPlayableMatchups = LeagueRequestPayload.calculateMaximumPlayableMatchups( gameDays: gameDays, entryMatchupsPerGameDay: entryMatchupsPerGameDay, @@ -97,7 +98,7 @@ extension ScheduleTestsProtocol { let times:LeagueTimeIndex = LeagueTimeIndex(startingTimes.count) let timeSlots:BitSet64 = .init(0.. = .init(0.., BitSet64>>.init( + let generalSettings = LeagueGeneralSettings.Runtime.init( gameGap: gameGaps, timeSlots: LeagueTimeIndex(startingTimes.count), startingTimes: startingTimes, @@ -122,7 +123,7 @@ extension ScheduleTestsProtocol { ) ) - var daySettings = [LeagueGeneralSettings.Runtime, BitSet64>>]() + var daySettings = [LeagueGeneralSettings.Runtime]() daySettings.reserveCapacity(gameDays) for day in 0.. Date: Sat, 7 Mar 2026 21:54:05 -0600 Subject: [PATCH 10/33] use a bit set for entry ids if possible --- .../league-scheduling/LeagueSchedule.swift | 50 +++++----- .../league-scheduling/data/AssignSlots.swift | 6 +- .../data/AssignSlotsB2B.swift | 6 +- .../data/BalanceHomeAway.swift | 4 +- .../data/LeagueScheduleData.swift | 81 ++++------------ .../data/LeagueScheduleDataSnapshot.swift | 15 +-- .../league-scheduling/data/MatchupBlock.swift | 43 +++++---- .../data/PrioritizedMatchups.swift | 8 +- .../data/RedistributionData.swift | 4 +- .../data/RemainingAllocations.swift | 18 ++-- .../data/SelectMatchup.swift | 6 +- .../data/assignment/Assign.swift | 8 +- .../assignmentState/AssignmentState.swift | 12 +-- .../ScheduleConfiguration.swift | 8 +- .../LeagueRequestPayload+Generate.swift | 96 +++++++++++++------ .../LeagueGeneralSettings+Runtime.swift | 4 +- .../LeagueRequestPayload+Runtime.swift | 4 +- .../league-scheduling/util/AbstractSet.swift | 65 ++++++++++++- .../league-scheduling/util/BitSet128.swift | 47 ++++++++- Sources/league-scheduling/util/BitSet64.swift | 47 ++++++++- .../util/ScheduleTestsProtocol.swift | 2 +- 21 files changed, 349 insertions(+), 185 deletions(-) diff --git a/Sources/league-scheduling/LeagueSchedule.swift b/Sources/league-scheduling/LeagueSchedule.swift index 18e50d8..b539ff7 100644 --- a/Sources/league-scheduling/LeagueSchedule.swift +++ b/Sources/league-scheduling/LeagueSchedule.swift @@ -7,8 +7,8 @@ import Foundation // TODO: support divisions on the same day with different times enum LeagueSchedule: Sendable, ~Copyable { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) static func generate( _ settings: borrowing LeagueRequestPayload.Runtime ) async -> LeagueGenerationResult { @@ -37,18 +37,18 @@ enum LeagueSchedule: Sendable, ~Copyable { // MARK: Generate schedules extension LeagueSchedule { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) static func generateSchedules( settings: borrowing LeagueRequestPayload.Runtime ) async throws -> [LeagueGenerationData] { let divisionsCount = settings.divisions.count - var divisionEntries:ContiguousArray> = .init(repeating: Set(), count: divisionsCount) + var divisionEntries:ContiguousArray = .init(repeating: .init(), count: divisionsCount) #if LOG print("LeagueSchedule;generateSchedules;divisionsCount=\(divisionsCount);settings.entries.count=\(settings.entries.count)") #endif for entryIndex in 0..]() + var grouped = [LeagueDayOfWeek:Config.EntryIDSet]() for (divisionID, division) in settings.divisions.enumerated() { - grouped[LeagueDayOfWeek(division.dayOfWeek), default: []].formUnion(divisionEntries[divisionID]) + grouped[LeagueDayOfWeek(division.dayOfWeek), default: .init()].formUnion(divisionEntries[divisionID]) } let finalMaxStartingTimes = maxStartingTimes let finalMaxLocations = maxLocations @@ -148,8 +148,8 @@ extension LeagueSchedule { // MARK: Generate schedule extension LeagueSchedule { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) private static func generateSchedule( dayOfWeek: LeagueDayOfWeek, settings: borrowing LeagueRequestPayload.Runtime, @@ -157,7 +157,7 @@ extension LeagueSchedule { divisionsCount: Int, maxStartingTimes: LeagueTimeIndex, maxLocations: LeagueLocationIndex, - scheduledEntries: Set + scheduledEntries: Config.EntryIDSet ) -> LeagueGenerationData { let gameDays = settings.gameDays var generationData = LeagueGenerationData() @@ -166,7 +166,7 @@ extension LeagueSchedule { generationData.schedule = .init(repeating: Set(), count: gameDays) var dataSnapshot = copy dataSnapshot - var gameDayDivisionEntries:ContiguousArray>> = .init(repeating: .init(repeating: Set(), count: divisionsCount), count: gameDays) + var gameDayDivisionEntries:ContiguousArray> = .init(repeating: .init(repeating: .init(), count: divisionsCount), count: gameDays) loadMaxAllocations( dataSnapshot: &dataSnapshot, gameDayDivisionEntries: &gameDayDivisionEntries, @@ -261,8 +261,8 @@ extension LeagueSchedule { return generationData } - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) private static func finalizeGenerationData( generationData: inout LeagueGenerationData, data: borrowing LeagueScheduleData @@ -283,17 +283,17 @@ extension LeagueSchedule { // MARK: Load max allocations extension LeagueSchedule { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) static func loadMaxAllocations( dataSnapshot: inout LeagueScheduleDataSnapshot, - gameDayDivisionEntries: inout ContiguousArray>>, + gameDayDivisionEntries: inout ContiguousArray>, settings: borrowing LeagueRequestPayload.Runtime, maxStartingTimes: LeagueTimeIndex, maxLocations: LeagueLocationIndex, - scheduledEntries: Set + scheduledEntries: Config.EntryIDSet ) { - for entryIndex in scheduledEntries { + scheduledEntries.forEach { entryIndex in let entry = settings.entries[unchecked: entryIndex] var maxPossiblePlayed:LeagueEntryMatchupsPerGameDay = 0 var maxStartingTimesPlayedAt = 0 @@ -327,7 +327,7 @@ extension LeagueSchedule { } } maxLocationsPlayedAt = max(maxLocationsPlayedAt, playable) - gameDayDivisionEntries[unchecked: day][unchecked: entry.division].insert(entry.id) + gameDayDivisionEntries[unchecked: day][unchecked: entry.division].insertMember(entry.id) } maxStartingTimesPlayedAt = max(maxStartingTimesPlayedAt, 1) maxLocationsPlayedAt = max(maxLocationsPlayedAt, 1) @@ -401,20 +401,20 @@ extension LeagueSchedule { // MARK: Maximum same opponent matchups extension LeagueSchedule { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) static func maximumSameOpponentMatchups( gameDays: LeagueDayIndex, entriesCount: Int, - divisionEntries: ContiguousArray>, + divisionEntries: ContiguousArray, divisions: [Config.DivisionRuntime] ) -> (LeagueMaximumSameOpponentMatchups, Config?) { var maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchups = .init(repeating: .init(repeating: .max, count: entriesCount), count: entriesCount) for (divisionIndex, division) in divisions.enumerated() { let divisionEntries = divisionEntries[divisionIndex] let cap = division.maxSameOpponentMatchups - for entryID in divisionEntries { - for opponentEntryID in divisionEntries { + divisionEntries.forEach { entryID in + divisionEntries.forEach { opponentEntryID in maxSameOpponentMatchups[unchecked: entryID][unchecked: opponentEntryID] = cap } } diff --git a/Sources/league-scheduling/data/AssignSlots.swift b/Sources/league-scheduling/data/AssignSlots.swift index 2367ff4..2bbe37b 100644 --- a/Sources/league-scheduling/data/AssignSlots.swift +++ b/Sources/league-scheduling/data/AssignSlots.swift @@ -53,7 +53,7 @@ extension LeagueScheduleData { var assignmentIndex = 0 var fms = failedMatchupSelections[unchecked: assignmentIndex] var optimalAvailableMatchups = assignmentState.availableMatchups.filter { !fms.contains($0) } - var prioritizedMatchups = PrioritizedMatchups( + var prioritizedMatchups = PrioritizedMatchups( entriesCount: entriesCount, prioritizedEntries: assignmentState.prioritizedEntries, availableMatchups: optimalAvailableMatchups @@ -143,7 +143,7 @@ extension LeagueScheduleData { canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) -> LeagueMatchup? { var pair:LeagueMatchupPair? = nil - var prioritizedMatchups = PrioritizedMatchups( + var prioritizedMatchups = PrioritizedMatchups( entriesCount: entriesCount, prioritizedEntries: assignmentState.prioritizedEntries, availableMatchups: assignmentState.availableMatchups @@ -192,7 +192,7 @@ extension LeagueScheduleData { canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) -> LeagueMatchup? { var pair:LeagueMatchupPair? = nil - var prioritizedMatchups = PrioritizedMatchups( + var prioritizedMatchups = PrioritizedMatchups( entriesCount: entriesCount, prioritizedEntries: assignmentState.prioritizedEntries, availableMatchups: assignmentState.availableMatchups diff --git a/Sources/league-scheduling/data/AssignSlotsB2B.swift b/Sources/league-scheduling/data/AssignSlotsB2B.swift index 5467a0a..4932266 100644 --- a/Sources/league-scheduling/data/AssignSlotsB2B.swift +++ b/Sources/league-scheduling/data/AssignSlotsB2B.swift @@ -20,10 +20,10 @@ extension LeagueScheduleData { let division = LeagueDivision.IDValue(divisionIndex) let divisionMatchups = assignmentState.allDivisionMatchups[unchecked: division] assignmentState.availableMatchups = divisionMatchups - assignmentState.prioritizedEntries.removeAll(keepingCapacity: true) + assignmentState.prioritizedEntries.removeAllKeepingCapacity() for matchup in assignmentState.availableMatchups { - assignmentState.prioritizedEntries.insert(matchup.team1) - assignmentState.prioritizedEntries.insert(matchup.team2) + assignmentState.prioritizedEntries.insertMember(matchup.team1) + assignmentState.prioritizedEntries.insertMember(matchup.team2) } assignmentState.recalculateAllRemainingAllocations( day: day, diff --git a/Sources/league-scheduling/data/BalanceHomeAway.swift b/Sources/league-scheduling/data/BalanceHomeAway.swift index 656c690..6a8619c 100644 --- a/Sources/league-scheduling/data/BalanceHomeAway.swift +++ b/Sources/league-scheduling/data/BalanceHomeAway.swift @@ -1,8 +1,8 @@ extension LeagueMatchupPair { /// Balances home/away allocations, mutating `team1` (home) and `team2` (away) if necessary. - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) mutating func balanceHomeAway( assignmentState: borrowing AssignmentState ) { diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index 10466d9..1fa44a7 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -39,8 +39,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { var redistributionData:RedistributionData? var redistributedMatchups = false - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) init( snapshot: LeagueScheduleDataSnapshot ) { @@ -64,8 +64,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { // MARK: Snapshot extension LeagueScheduleData { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) mutating func loadSnapshot(_ snapshot: LeagueScheduleDataSnapshot) { //locations = snapshot.locations divisionRecurringDayLimitInterval = snapshot.divisionRecurringDayLimitInterval @@ -81,8 +81,8 @@ extension LeagueScheduleData { shuffleHistory = snapshot.shuffleHistory } - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) func snapshot() -> LeagueScheduleDataSnapshot { return .init(self) } @@ -96,12 +96,12 @@ extension LeagueScheduleData { /// - day: Day index that will be scheduled. /// - divisionEntries: Division entries that play on the `day`. (`LeagueDivision.IDValue`: `Set`) /// - entryMatchupsPerGameDay: Number of times a single team will play on `day`. - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) mutating func newDay( day: LeagueDayIndex, daySettings: LeagueGeneralSettings.Runtime, - divisionEntries: ContiguousArray>, + divisionEntries: ContiguousArray, availableSlots: Set, settings: borrowing LeagueRequestPayload.Runtime, generationData: inout LeagueGenerationData @@ -117,7 +117,8 @@ extension LeagueScheduleData { self.gameGap = daySettings.gameGap.minMax self.sameLocationIfB2B = daySettings.sameLocationIfB2B var availableMatchups = Set() - var prioritizedEntries = Set(minimumCapacity: entriesCount) + var prioritizedEntries = Config.EntryIDSet() + prioritizedEntries.reserveCapacity(entriesCount) var entryCountsForDivision:ContiguousArray = .init(repeating: 0, count: divisionEntries.count) expectedMatchupsCount = 0 assignmentState.allDivisionMatchups = .init(repeating: [], count: divisionEntries.count) @@ -128,12 +129,9 @@ extension LeagueScheduleData { entryMatchupsPerGameDay: defaultMaxEntryMatchupsPerGameDay ) - var iterator = entriesInDivision.makeIterator() - while let entryID = iterator.next() { - if assignmentState.numberOfAssignedMatchups[unchecked: entryID] >= daySettings.maximumPlayableMatchups[unchecked: entryID] { - entriesInDivision.remove(entryID) - } - } + entriesInDivision.removeAll(where: { + assignmentState.numberOfAssignedMatchups[unchecked: $0] >= daySettings.maximumPlayableMatchups[unchecked: $0] + }) entryCountsForDivision[divisionIndex] = entriesInDivision.count expectedMatchupsCount += (entriesInDivision.count * defaultMaxEntryMatchupsPerGameDay) / entriesPerMatchup @@ -141,7 +139,10 @@ extension LeagueScheduleData { #if LOG print("LeagueScheduleData;newDay;day=\(day);expectedMatchupsCount=\(expectedMatchupsCount);divisionIndex=\(divisionIndex);entryCountsForDivision=\(entriesInDivision.count);divisionRecurringDayLimitInterval=\(divisionRecurringDayLimitInterval[divisionIndex])") #endif - let availableDivisionMatchups = availableMatchupPairs(for: entriesInDivision) + let availableDivisionMatchups = entriesInDivision.availableMatchupPairs( + assignedEntryHomeAways: assignmentState.assignedEntryHomeAways, + maxSameOpponentMatchups: assignmentState.maxSameOpponentMatchups + ) self.assignmentState.allDivisionMatchups[divisionIndex] = availableDivisionMatchups availableMatchups.formUnion(availableDivisionMatchups) } @@ -164,10 +165,10 @@ extension LeagueScheduleData { assignmentState.playsAt[unchecked: i].removeAll(keepingCapacity: true) } for i in 0.. - ) -> Set { - return Self.availableMatchupPairs( - for: entries, - assignedEntryHomeAways: assignmentState.assignedEntryHomeAways, - maxSameOpponentMatchups: assignmentState.maxSameOpponentMatchups - ) - } - - /// - Parameters: - /// - entries: Entries that will participate in matchup scheduling. - /// - Returns: The available matchup pairs that can play for the `day`. - static func availableMatchupPairs( - for entries: Set, - assignedEntryHomeAways: AssignedEntryHomeAways, - maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups - ) -> Set { - var pairs = Set(minimumCapacity: (entries.count-1) * 2) - let sortedEntries = entries.sorted() - - var index = 0 - while index < sortedEntries.count - 1 { - let home = sortedEntries[index] - index += 1 - let assignedHome = assignedEntryHomeAways[unchecked: home] - let maxSameOpponentMatchups = maxSameOpponentMatchups[unchecked: home] - for away in sortedEntries[index...] { - if assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { - pairs.insert(.init(team1: home, team2: away)) - } - } - } - return pairs - } -} - // MARK: Get recurring day limit interval extension LeagueScheduleData { static func recurringDayLimitInterval( diff --git a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift index f162a11..3f8527f 100644 --- a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift +++ b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift @@ -27,8 +27,8 @@ struct LeagueScheduleDataSnapshot: Sendable { var executionSteps = [ExecutionStep]() var shuffleHistory = [LeagueShuffleAction]() - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) init( maxStartingTimes: LeagueTimeIndex, startingTimes: [StaticTime], @@ -36,7 +36,7 @@ struct LeagueScheduleDataSnapshot: Sendable { entriesPerMatchup: LeagueEntriesPerMatchup, maximumPlayableMatchups: [UInt32], entries: [Config.EntryRuntime], - divisionEntries: ContiguousArray>, + divisionEntries: ContiguousArray, matchupDuration: LeagueMatchupDuration, gameGap: (Int, Int), sameLocationIfB2B: Bool, @@ -48,11 +48,12 @@ struct LeagueScheduleDataSnapshot: Sendable { self.gameGap = gameGap self.sameLocationIfB2B = sameLocationIfB2B - var prioritizedEntries = Set(minimumCapacity: entriesCount) + var prioritizedEntries = Config.EntryIDSet() + prioritizedEntries.reserveCapacity(entriesCount) var entryDivisions = ContiguousArray(repeating: 0, count: entriesCount) for (index, entries) in divisionEntries.enumerated() { prioritizedEntries.formUnion(entries) - for entry in entries { + entries.forEach { entry in entryDivisions[unchecked: entry] = LeagueDivision.IDValue(index) } } @@ -89,8 +90,8 @@ struct LeagueScheduleDataSnapshot: Sendable { ) } - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) init(_ snapshot: borrowing LeagueScheduleData) { entriesPerMatchup = snapshot.entriesPerMatchup entriesCount = snapshot.entriesCount diff --git a/Sources/league-scheduling/data/MatchupBlock.swift b/Sources/league-scheduling/data/MatchupBlock.swift index 7f5a4b8..c3211f7 100644 --- a/Sources/league-scheduling/data/MatchupBlock.swift +++ b/Sources/league-scheduling/data/MatchupBlock.swift @@ -72,8 +72,9 @@ extension LeagueScheduleData { print("assignedEntryHomeAways=\(localAssignmentState.assignedEntryHomeAways.map { $0.map { $0.sum } })") #endif // assign initial matchups - var adjacentTimes = BitSet64() - var selectedEntries = Set(minimumCapacity: amount * entriesPerMatchup) + var adjacentTimes = Config.TimeSet() + var selectedEntries = Config.EntryIDSet() + selectedEntries.reserveCapacity(amount * entriesPerMatchup) // assign the first matchup, prioritizing the matchup's time guard let firstMatchup = selectAndAssignMatchup( @@ -126,22 +127,22 @@ extension LeagueScheduleData { // assign the last matchup let lastLocalAssignmentStateAvailableMatchups = localAssignmentState.availableMatchups let lastSelectedEntries = selectedEntries - let shouldSkipSelection:(LeagueMatchupPair) -> Bool = entryMatchupsPerGameDay % 2 == 0 ? { + let shouldSkipSelection:(LeagueMatchupPair) -> Bool = entryMatchupsPerGameDay % 2 == 0 ? { pair in var targetEntries = lastSelectedEntries - targetEntries.insert($0.team1) - targetEntries.insert($0.team2) + targetEntries.insertMember(pair.team1) + targetEntries.insertMember(pair.team2) let availableMatchups = lastLocalAssignmentStateAvailableMatchups.filter { targetEntries.contains($0.team1) && targetEntries.contains($0.team2) } - for entryID in targetEntries { + return targetEntries.forEachWithReturn { entryID in if availableMatchups.first(where: { $0.team1 == entryID || $0.team2 == entryID }) == nil { #if LOG - print("assignBlockOfMatchups;i == lastMatchupIndex;$0=\($0);targetEntries (\(targetEntries.count))=\(targetEntries);entryID=\(entryID);availableMatchups.first of entryID == nil;skipping $0") + print("assignBlockOfMatchups;i == lastMatchupIndex;pair=\(pair);targetEntries (\(targetEntries.count))=\(targetEntries);entryID=\(entryID);availableMatchups.first of entryID == nil;skipping $0") #endif return true } - } - return false + return nil + } ?? false } : { _ in false } guard let _ = selectAndAssignMatchup( day: day, @@ -223,8 +224,8 @@ extension LeagueScheduleData { divisionRecurringDayLimitInterval: ContiguousArray, allAvailableMatchups: Set, localAssignmentState: inout AssignmentState, - remainingPrioritizedEntries: inout Set, - selectedEntries: inout Set, + remainingPrioritizedEntries: inout Config.EntryIDSet, + selectedEntries: inout Config.EntryIDSet, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) -> LeagueMatchup? { @@ -244,10 +245,10 @@ extension LeagueScheduleData { return nil } // successfully assigned - remainingPrioritizedEntries.remove(leagueMatchup.home) - remainingPrioritizedEntries.remove(leagueMatchup.away) - selectedEntries.insert(leagueMatchup.home) - selectedEntries.insert(leagueMatchup.away) + remainingPrioritizedEntries.removeMember(leagueMatchup.home) + remainingPrioritizedEntries.removeMember(leagueMatchup.away) + selectedEntries.removeMember(leagueMatchup.home) + selectedEntries.removeMember(leagueMatchup.away) return leagueMatchup } private static func selectAndAssignMatchup( @@ -261,8 +262,8 @@ extension LeagueScheduleData { allAvailableMatchups: Set, localAssignmentState: inout AssignmentState, shouldSkipSelection: (LeagueMatchupPair) -> Bool, - remainingPrioritizedEntries: inout Set, - selectedEntries: inout Set, + remainingPrioritizedEntries: inout Config.EntryIDSet, + selectedEntries: inout Config.EntryIDSet, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) -> LeagueMatchup? { @@ -283,10 +284,10 @@ extension LeagueScheduleData { return nil } // successfully assigned - remainingPrioritizedEntries.remove(leagueMatchup.home) - remainingPrioritizedEntries.remove(leagueMatchup.away) - selectedEntries.insert(leagueMatchup.home) - selectedEntries.insert(leagueMatchup.away) + remainingPrioritizedEntries.removeMember(leagueMatchup.home) + remainingPrioritizedEntries.removeMember(leagueMatchup.away) + selectedEntries.removeMember(leagueMatchup.home) + selectedEntries.removeMember(leagueMatchup.away) return leagueMatchup } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/PrioritizedMatchups.swift b/Sources/league-scheduling/data/PrioritizedMatchups.swift index 6d8e177..63cb494 100644 --- a/Sources/league-scheduling/data/PrioritizedMatchups.swift +++ b/Sources/league-scheduling/data/PrioritizedMatchups.swift @@ -1,11 +1,11 @@ -struct PrioritizedMatchups: Sendable, ~Copyable { +struct PrioritizedMatchups: Sendable, ~Copyable { private(set) var matchups:Set private(set) var availableMatchupCountForEntry:ContiguousArray init( entriesCount: Int, - prioritizedEntries: Set, + prioritizedEntries: Config.EntryIDSet, availableMatchups: Set ) { let matchups = Self.filterMatchups(prioritizedEntries: prioritizedEntries, availableMatchups: availableMatchups) @@ -19,7 +19,7 @@ struct PrioritizedMatchups: Sendable, ~Copyable { } mutating func update( - prioritizedEntries: Set, + prioritizedEntries: Config.EntryIDSet, availableMatchups: Set ) { matchups = Self.filterMatchups(prioritizedEntries: prioritizedEntries, availableMatchups: availableMatchups) @@ -38,7 +38,7 @@ struct PrioritizedMatchups: Sendable, ~Copyable { } private static func filterMatchups( - prioritizedEntries: Set, + prioritizedEntries: Config.EntryIDSet, availableMatchups: Set ) -> Set { if prioritizedEntries.isEmpty { diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index fdd84f1..f3abfd6 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -10,8 +10,8 @@ struct RedistributionData: Sendable { private var redistributedEntries:[UInt16] private(set) var redistributed:Set - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) init( dayIndex: LeagueDayIndex, startDayIndex: LeagueDayIndex, diff --git a/Sources/league-scheduling/data/RemainingAllocations.swift b/Sources/league-scheduling/data/RemainingAllocations.swift index 344dc11..bef65b8 100644 --- a/Sources/league-scheduling/data/RemainingAllocations.swift +++ b/Sources/league-scheduling/data/RemainingAllocations.swift @@ -5,7 +5,8 @@ extension AssignmentState { entriesCount: Int ) { remainingAllocations = .init(repeating: availableSlots, count: entriesCount) - var cached = Set(minimumCapacity: entriesCount) + var cached = Config.EntryIDSet() + cached.reserveCapacity(entriesCount) for matchup in availableMatchups { recalculateNewDayRemainingAllocations( for: matchup, @@ -19,7 +20,7 @@ extension AssignmentState { private mutating func recalculateNewDayRemainingAllocations( for pair: LeagueMatchupPair, - cached: inout Set + cached: inout Config.EntryIDSet ) { recalculateNewDayRemainingAllocations( for: pair.team1, @@ -32,10 +33,10 @@ extension AssignmentState { } private mutating func recalculateNewDayRemainingAllocations( for team: LeagueEntry.IDValue, - cached: inout Set + cached: inout Config.EntryIDSet ) { guard !cached.contains(team) else { return } - cached.insert(team) + cached.insertMember(team) let timeNumbers = assignedTimes[unchecked: team] let locationNumbers = assignedLocations[unchecked: team] let maxTimeNumbers = maxTimeAllocations[unchecked: team] @@ -58,7 +59,8 @@ extension AssignmentState { gameGap: GameGap.TupleValue, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) { - var cached = Set(minimumCapacity: entriesCount) + var cached = Config.EntryIDSet() + cached.reserveCapacity(entriesCount) for matchup in availableMatchups { recalculateRemainingAllocations( day: day, @@ -76,7 +78,7 @@ extension AssignmentState { private mutating func recalculateRemainingAllocations( day: LeagueDayIndex, for pair: LeagueMatchupPair, - cached: inout Set, + cached: inout Config.EntryIDSet, gameGap: GameGap.TupleValue, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) { @@ -99,12 +101,12 @@ extension AssignmentState { private mutating func recalculateRemainingAllocations( day: LeagueDayIndex, for team: LeagueEntry.IDValue, - cached: inout Set, + cached: inout Config.EntryIDSet, gameGap: GameGap.TupleValue, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) { guard !cached.contains(team) else { return } - cached.insert(team) + cached.insertMember(team) let allowedTimes = entries[unchecked: team].gameTimes[unchecked: day] let allowedLocations = entries[unchecked: team].gameLocations[unchecked: day] let playsAt = playsAt[unchecked: team] diff --git a/Sources/league-scheduling/data/SelectMatchup.swift b/Sources/league-scheduling/data/SelectMatchup.swift index 09335f5..d56264f 100644 --- a/Sources/league-scheduling/data/SelectMatchup.swift +++ b/Sources/league-scheduling/data/SelectMatchup.swift @@ -2,7 +2,7 @@ // MARK: Select matchup extension LeagueScheduleData { /// - Returns: Matchup pair that should be prioritized to be scheduled due to how many allocations it has remaining. - func selectMatchup(prioritizedMatchups: borrowing PrioritizedMatchups) -> LeagueMatchupPair? { + func selectMatchup(prioritizedMatchups: borrowing PrioritizedMatchups) -> LeagueMatchupPair? { return assignmentState.selectMatchup(prioritizedMatchups: prioritizedMatchups) } } @@ -10,7 +10,7 @@ extension LeagueScheduleData { extension AssignmentState { /// - Returns: Matchup pair that should be prioritized to be scheduled due to how many allocations it has remaining. func selectMatchup( - prioritizedMatchups: borrowing PrioritizedMatchups + prioritizedMatchups: borrowing PrioritizedMatchups ) -> LeagueMatchupPair? { return Self.selectMatchup( prioritizedMatchups: prioritizedMatchups, @@ -22,7 +22,7 @@ extension AssignmentState { /// - Returns: Matchup pair that should be prioritized to be scheduled due to how many allocations it has remaining. static func selectMatchup( - prioritizedMatchups: borrowing PrioritizedMatchups, + prioritizedMatchups: borrowing PrioritizedMatchups, numberOfAssignedMatchups: [Int], recurringDayLimits: RecurringDayLimits, remainingAllocations: RemainingAllocations diff --git a/Sources/league-scheduling/data/assignment/Assign.swift b/Sources/league-scheduling/data/assignment/Assign.swift index 625ab40..699ac49 100644 --- a/Sources/league-scheduling/data/assignment/Assign.swift +++ b/Sources/league-scheduling/data/assignment/Assign.swift @@ -41,8 +41,8 @@ extension AssignmentState { divisionRecurringDayLimitInterval: ContiguousArray, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) -> LeagueMatchup { - prioritizedEntries.remove(matchup.team1) - prioritizedEntries.remove(matchup.team2) + prioritizedEntries.removeMember(matchup.team1) + prioritizedEntries.removeMember(matchup.team2) let home:LeagueEntry.IDValue = matchup.team1 let away:LeagueEntry.IDValue = matchup.team2 incrementRecurringDayLimits(home: home, away: away, entryDivisions: entryDivisions, divisionRecurringDayLimitInterval: divisionRecurringDayLimitInterval) @@ -63,13 +63,13 @@ extension AssignmentState { availableMatchups.remove(.init(team1: matchup.team2, team2: matchup.team1)) if playsAtTimes[unchecked: home].count == entryMatchupsPerGameDay { #if LOG - remainingAllocations[unchecked: home].removeAll() + remainingAllocations[unchecked: home].removeAllKeepingCapacity() #endif availableMatchups = availableMatchups.filter({ $0.team1 != home && $0.team2 != home }) } if playsAtTimes[unchecked: away].count == entryMatchupsPerGameDay { #if LOG - remainingAllocations[unchecked: away].removeAll() + remainingAllocations[unchecked: away].removeAllKeepingCapacity() #endif availableMatchups = availableMatchups.filter({ $0.team1 != away && $0.team2 != away }) } diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift index 6d54d3a..7bea08c 100644 --- a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift +++ b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift @@ -55,7 +55,7 @@ struct AssignmentState: Sendable, ~Copyable { /// Remaining available matchup pairs that can be assigned for the `day`. var availableMatchups:Set - var prioritizedEntries:Set + var prioritizedEntries:Config.EntryIDSet /// Remaining available slots that can be filled for the `day`. var availableSlots:Set @@ -69,8 +69,8 @@ struct AssignmentState: Sendable, ~Copyable { var shuffleHistory = [LeagueShuffleAction]() - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) func copyable() -> AssignmentStateCopyable { return .init( entries: entries, @@ -176,7 +176,7 @@ struct AssignmentStateCopyable { /// Remaining available matchup pairs that can be assigned for the `day`. var availableMatchups:Set - var prioritizedEntries:Set + var prioritizedEntries:Config.EntryIDSet /// Remaining available slots that can be filled for the `day`. var availableSlots:Set @@ -188,8 +188,8 @@ struct AssignmentStateCopyable { var shuffleHistory:[LeagueShuffleAction] - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) func noncopyable() -> AssignmentState { return .init( entries: entries, diff --git a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift index 1b79403..3272f21 100644 --- a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift +++ b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift @@ -3,10 +3,16 @@ protocol ScheduleConfiguration: Sendable, ~Copyable { associatedtype DaySet:SetOfDayIndexes associatedtype TimeSet:SetOfTimeIndexes associatedtype LocationSet:SetOfLocationIndexes + associatedtype EntryIDSet:SetOfEntryIDs typealias DivisionRuntime = LeagueDivision.Runtime typealias EntryRuntime = LeagueEntry.Runtime } -enum ScheduleConfig: ScheduleConfiguration { +enum ScheduleConfig< + DaySet: SetOfDayIndexes, + TimeSet: SetOfTimeIndexes, + LocationSet: SetOfLocationIndexes, + EntryIDSet: SetOfEntryIDs + >: ScheduleConfiguration { } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index 4b5551a..12ed090 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -122,12 +122,12 @@ extension LeagueRequestPayload { ) } } - private func generate( + private func generate( defaultGameGap: GameGap, divisionsCount: Int, startingDayOfWeek: LeagueDayOfWeek, d: DaySet, - t: Times, + t: TimeSet ) async throws(LeagueError) -> LeagueGenerationResult { switch settings.locations { case 1...64: @@ -135,42 +135,84 @@ extension LeagueRequestPayload { defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, - defaultGameDays: d, - defaultTimes: t, - defaultLocations: BitSet64() + d: d, + t: t, + l: BitSet64() + ) + case 65...128: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + d: d, + t: t, + l: BitSet128() + ) + default: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + d: d, + t: t, + l: Set() + ) + } + } + private func generate( + defaultGameGap: GameGap, + divisionsCount: Int, + startingDayOfWeek: LeagueDayOfWeek, + d: DaySet, + t: TimeSet, + l: LocationSet + ) async throws(LeagueError) -> LeagueGenerationResult { + switch entries.count { + case 1...64: + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + d: d, + t: t, + l: l, + e: BitSet64() ) case 65...128: return try await generate( defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, - defaultGameDays: d, - defaultTimes: t, - defaultLocations: BitSet128() + d: d, + t: t, + l: l, + e: BitSet128() ) default: return try await generate( defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, - defaultGameDays: d, - defaultTimes: t, - defaultLocations: Set() + d: d, + t: t, + l: l, + e: Set() ) } } } extension LeagueRequestPayload { - private func generate( + private func generate( defaultGameGap: GameGap, divisionsCount: Int, startingDayOfWeek: LeagueDayOfWeek, - defaultGameDays: DaySet, - defaultTimes: Times, - defaultLocations: Locations + d: DaySet, + t: TimeSet, + l: LocationSet, + e: EntryIDSet ) async throws(LeagueError) -> LeagueGenerationResult { - let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) + let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) var teamsForDivision = [Int](repeating: 0, count: divisionsCount) let entries = try parseEntries( divisionsCount: divisionsCount, @@ -193,7 +235,7 @@ extension LeagueRequestPayload { maximumPlayableMatchups: settings.maximumPlayableMatchups.array ) - let timesSet = Times(0..>( + general: LeagueGeneralSettings.Runtime>( gameGap: defaultGameGap, timeSlots: settings.timeSlots, startingTimes: settings.startingTimes.times, @@ -249,8 +291,8 @@ extension LeagueRequestPayload { } extension LeagueRequestPayload { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) private func generate( divisions: [Config.DivisionRuntime], entries: [Config.EntryRuntime], @@ -492,8 +534,8 @@ extension LeagueRequestPayload { // MARK: Parse day settings extension LeagueRequestPayload { - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) private func parseDaySettings( general: LeagueGeneralSettings.Runtime, correctMaximumPlayableMatchups: [UInt32], diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index cde4af9..f126a91 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -154,8 +154,8 @@ extension LeagueGeneralSettings.Runtime { balancedLocations.contains(location) } - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) init( gameGap: GameGap, timeSlots: LeagueTimeIndex, diff --git a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift index 946de7d..0068a6e 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift @@ -28,8 +28,8 @@ extension LeagueRequestPayload { /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] let daySettings:[LeagueGeneralSettings.Runtime] - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) init( gameDays: LeagueDayIndex, divisions: [Config.DivisionRuntime], diff --git a/Sources/league-scheduling/util/AbstractSet.swift b/Sources/league-scheduling/util/AbstractSet.swift index b8a3259..6debfd1 100644 --- a/Sources/league-scheduling/util/AbstractSet.swift +++ b/Sources/league-scheduling/util/AbstractSet.swift @@ -12,6 +12,8 @@ protocol AbstractSet: Sendable, ~Copyable { /// in the set. func contains(_ member: Element) -> Bool + mutating func reserveCapacity(_ minimumCapacity: Int) + /// Inserts the given element in the set if it is not already present. mutating func insertMember(_ member: Element) @@ -19,24 +21,62 @@ protocol AbstractSet: Sendable, ~Copyable { mutating func removeMember(_ member: Element) mutating func removeAll() + mutating func removeAllKeepingCapacity() + mutating func removeAll(where condition: (Element) throws -> Bool) rethrows + + mutating func formUnion(_ other: borrowing Self) + + func randomElement() -> Element? func forEach(_ body: (Element) throws -> Void) rethrows + func forEachWithReturn(_ body: (Element) throws -> Result?) rethrows -> Result? } protocol SetOfDayIndexes: AbstractSet, ~Copyable where Element == LeagueDayIndex {} protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex {} protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LeagueLocationIndex {} +protocol SetOfEntryIDs: AbstractSet, ~Copyable where Element == LeagueEntry.IDValue { + /// - Returns: The available matchup pairs that can play for the `day`. + func availableMatchupPairs( + assignedEntryHomeAways: AssignedEntryHomeAways, + maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups + ) -> Set +} + extension Set: AbstractSet { @inline(__always) mutating func removeMember(_ member: Element) { self.remove(member) } + @inline(__always) mutating func removeAll() { + self.removeAll(keepingCapacity: false) + } + @inline(__always) + mutating func removeAllKeepingCapacity() { self.removeAll(keepingCapacity: true) } + mutating func removeAll(where condition: (Element) throws -> Bool) rethrows { + var iterator = makeIterator() + while let next = iterator.next() { + if try condition(next) { + remove(next) + } + } + } + + func forEachWithReturn(_ body: (Element) throws -> Result?) rethrows -> Result? { + for e in self { + if let r = try body(e) { + return r + } + } + return nil + } + @inline(__always) mutating func insertMember(_ member: Element) { insert(member) @@ -45,4 +85,27 @@ extension Set: AbstractSet { extension Set: SetOfDayIndexes {} extension Set: SetOfTimeIndexes {} -extension Set: SetOfLocationIndexes {} \ No newline at end of file +extension Set: SetOfLocationIndexes {} + +extension Set: SetOfEntryIDs { + func availableMatchupPairs( + assignedEntryHomeAways: AssignedEntryHomeAways, + maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups + ) -> Set { + var pairs = Set(minimumCapacity: (count-1) * 2) + let sortedEntries = sorted() + var index = 0 + while index < sortedEntries.count - 1 { + let home = sortedEntries[index] + index += 1 + let assignedHome = assignedEntryHomeAways[unchecked: home] + let maxSameOpponentMatchups = maxSameOpponentMatchups[unchecked: home] + for away in sortedEntries[index...] { + if assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { + pairs.insert(.init(team1: home, team2: away)) + } + } + } + return pairs + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index d4d2cf5..694dde9 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -22,6 +22,9 @@ struct BitSet128: Sendable { var isEmpty: Bool { storage == 0 } + + func reserveCapacity(_ minimumCapacity: Int) { + } } // MARK: contains @@ -50,6 +53,16 @@ extension BitSet128 { mutating func removeAll() { storage = 0 } + mutating func removeAllKeepingCapacity() { + storage = 0 + } + mutating func removeAll(where condition: (Element) throws -> Bool) rethrows { + try forEach { + if try condition($0) { + removeMember($0) + } + } + } } // MARK: iterator @@ -62,6 +75,17 @@ extension BitSet128 { temp &= (temp - 1) } } + func forEachWithReturn(_ body: (Element) throws -> Result?) rethrows -> Result? { + var temp = storage + while temp != 0 { + let index = temp.trailingZeroBitCount + if let r = try body(Element(index)) { + return r + } + temp &= (temp - 1) + } + return nil + } } // MARK: form union @@ -88,4 +112,25 @@ extension BitSet128 { extension BitSet128: AbstractSet {} extension BitSet128: SetOfDayIndexes where Element == LeagueTimeIndex {} extension BitSet128: SetOfTimeIndexes where Element == LeagueTimeIndex {} -extension BitSet128: SetOfLocationIndexes where Element == LeagueLocationIndex {} \ No newline at end of file +extension BitSet128: SetOfLocationIndexes where Element == LeagueLocationIndex {} + +extension BitSet128: SetOfEntryIDs where Element == LeagueEntry.IDValue { + func availableMatchupPairs( + assignedEntryHomeAways: AssignedEntryHomeAways, + maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups + ) -> Set { + var pairs = Set(minimumCapacity: (count-1) * 2) + var index = 0 + forEach { home in + let assignedHome = assignedEntryHomeAways[unchecked: home] + let maxSameOpponentMatchups = maxSameOpponentMatchups[unchecked: home] + index += 1 + forEach { away in + if away >= index, assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { + pairs.insert(.init(team1: home, team2: away)) + } + } + } + return pairs + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index e2b0fb5..4f9dfe4 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -28,6 +28,9 @@ struct BitSet64: Sendable { var isEmpty: Bool { storage == 0 } + + func reserveCapacity(_ minimumCapacity: Int) { + } } // MARK: contains @@ -56,6 +59,16 @@ extension BitSet64 { mutating func removeAll() { storage = 0 } + mutating func removeAllKeepingCapacity() { + storage = 0 + } + mutating func removeAll(where condition: (Element) throws -> Bool) rethrows { + try forEach { + if try condition($0) { + removeMember($0) + } + } + } } // MARK: iterator @@ -68,6 +81,17 @@ extension BitSet64 { temp &= (temp - 1) } } + func forEachWithReturn(_ body: (Element) throws -> Result?) rethrows -> Result? { + var temp = storage + while temp != 0 { + let index = temp.trailingZeroBitCount + if let r = try body(Element(index)) { + return r + } + temp &= (temp - 1) + } + return nil + } } // MARK: form union @@ -94,4 +118,25 @@ extension BitSet64 { extension BitSet64: AbstractSet {} extension BitSet64: SetOfDayIndexes where Element == LeagueTimeIndex {} extension BitSet64: SetOfTimeIndexes where Element == LeagueTimeIndex {} -extension BitSet64: SetOfLocationIndexes where Element == LeagueLocationIndex {} \ No newline at end of file +extension BitSet64: SetOfLocationIndexes where Element == LeagueLocationIndex {} + +extension BitSet64: SetOfEntryIDs where Element == LeagueEntry.IDValue { + func availableMatchupPairs( + assignedEntryHomeAways: AssignedEntryHomeAways, + maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups + ) -> Set { + var pairs = Set(minimumCapacity: (count-1) * 2) + var index = 0 + forEach { home in + let assignedHome = assignedEntryHomeAways[unchecked: home] + let maxSameOpponentMatchups = maxSameOpponentMatchups[unchecked: home] + index += 1 + forEach { away in + if away >= index, assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { + pairs.insert(.init(team1: home, team2: away)) + } + } + } + return pairs + } +} \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift index bca161e..dec913b 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift @@ -4,7 +4,7 @@ import struct FoundationEssentials.Date import StaticDateTimes protocol ScheduleTestsProtocol: ScheduleExpectations { - typealias UnitTestScheduleConfig = ScheduleConfig, BitSet64, BitSet64> + typealias UnitTestScheduleConfig = ScheduleConfig, BitSet64, BitSet64, BitSet64> typealias UnitTestRuntimeSchedule = LeagueRequestPayload.Runtime } From 3e5647a56020bb4d887c68d361837dafc5aab3b4 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Sun, 8 Mar 2026 05:51:49 -0500 Subject: [PATCH 11/33] don't explode the binary and compile time; add package trait to toggle specialization --- Package.swift | 6 +- .../league-scheduling/LeagueSchedule.swift | 24 ++- .../data/BalanceHomeAway.swift | 4 +- .../data/LeagueScheduleData.swift | 16 +- .../data/LeagueScheduleDataSnapshot.swift | 8 +- .../data/RedistributionData.swift | 4 +- .../assignmentState/AssignmentState.swift | 8 +- .../LeagueRequestPayload+Generate.swift | 166 ++++-------------- .../LeagueGeneralSettings+Runtime.swift | 4 +- .../LeagueRequestPayload+Runtime.swift | 4 +- 10 files changed, 93 insertions(+), 151 deletions(-) diff --git a/Package.swift b/Package.swift index 385ae95..29e79c5 100644 --- a/Package.swift +++ b/Package.swift @@ -12,9 +12,11 @@ let package = Package( ], traits: [ .default(enabledTraits: [ - "UncheckedArraySubscript" + "UncheckedArraySubscript", + "SpecializeScheduleConfiguration" ]), - .trait(name: "UncheckedArraySubscript") + .trait(name: "UncheckedArraySubscript"), + .trait(name: "SpecializeScheduleConfiguration") ], dependencies: [ .package(url: "https://github.com/RandomHashTags/swift-staticdatetime", from: "0.3.5"), diff --git a/Sources/league-scheduling/LeagueSchedule.swift b/Sources/league-scheduling/LeagueSchedule.swift index b539ff7..d8828c4 100644 --- a/Sources/league-scheduling/LeagueSchedule.swift +++ b/Sources/league-scheduling/LeagueSchedule.swift @@ -7,8 +7,10 @@ import Foundation // TODO: support divisions on the same day with different times enum LeagueSchedule: Sendable, ~Copyable { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif static func generate( _ settings: borrowing LeagueRequestPayload.Runtime ) async -> LeagueGenerationResult { @@ -37,8 +39,10 @@ enum LeagueSchedule: Sendable, ~Copyable { // MARK: Generate schedules extension LeagueSchedule { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif static func generateSchedules( settings: borrowing LeagueRequestPayload.Runtime ) async throws -> [LeagueGenerationData] { @@ -148,8 +152,10 @@ extension LeagueSchedule { // MARK: Generate schedule extension LeagueSchedule { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif private static func generateSchedule( dayOfWeek: LeagueDayOfWeek, settings: borrowing LeagueRequestPayload.Runtime, @@ -261,8 +267,10 @@ extension LeagueSchedule { return generationData } + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif private static func finalizeGenerationData( generationData: inout LeagueGenerationData, data: borrowing LeagueScheduleData @@ -283,8 +291,10 @@ extension LeagueSchedule { // MARK: Load max allocations extension LeagueSchedule { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif static func loadMaxAllocations( dataSnapshot: inout LeagueScheduleDataSnapshot, gameDayDivisionEntries: inout ContiguousArray>, @@ -401,8 +411,10 @@ extension LeagueSchedule { // MARK: Maximum same opponent matchups extension LeagueSchedule { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif static func maximumSameOpponentMatchups( gameDays: LeagueDayIndex, entriesCount: Int, diff --git a/Sources/league-scheduling/data/BalanceHomeAway.swift b/Sources/league-scheduling/data/BalanceHomeAway.swift index 6a8619c..de0b117 100644 --- a/Sources/league-scheduling/data/BalanceHomeAway.swift +++ b/Sources/league-scheduling/data/BalanceHomeAway.swift @@ -1,8 +1,10 @@ extension LeagueMatchupPair { /// Balances home/away allocations, mutating `team1` (home) and `team2` (away) if necessary. + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif mutating func balanceHomeAway( assignmentState: borrowing AssignmentState ) { diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index 1fa44a7..c3109e0 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -39,8 +39,10 @@ struct LeagueScheduleData: Sendable, ~Copyable { var redistributionData:RedistributionData? var redistributedMatchups = false + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif init( snapshot: LeagueScheduleDataSnapshot ) { @@ -64,8 +66,10 @@ struct LeagueScheduleData: Sendable, ~Copyable { // MARK: Snapshot extension LeagueScheduleData { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif mutating func loadSnapshot(_ snapshot: LeagueScheduleDataSnapshot) { //locations = snapshot.locations divisionRecurringDayLimitInterval = snapshot.divisionRecurringDayLimitInterval @@ -81,8 +85,10 @@ extension LeagueScheduleData { shuffleHistory = snapshot.shuffleHistory } + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif func snapshot() -> LeagueScheduleDataSnapshot { return .init(self) } @@ -96,8 +102,10 @@ extension LeagueScheduleData { /// - day: Day index that will be scheduled. /// - divisionEntries: Division entries that play on the `day`. (`LeagueDivision.IDValue`: `Set`) /// - entryMatchupsPerGameDay: Number of times a single team will play on `day`. + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif mutating func newDay( day: LeagueDayIndex, daySettings: LeagueGeneralSettings.Runtime, diff --git a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift index 3f8527f..2731ee2 100644 --- a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift +++ b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift @@ -27,8 +27,10 @@ struct LeagueScheduleDataSnapshot: Sendable { var executionSteps = [ExecutionStep]() var shuffleHistory = [LeagueShuffleAction]() + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif init( maxStartingTimes: LeagueTimeIndex, startingTimes: [StaticTime], @@ -90,8 +92,10 @@ struct LeagueScheduleDataSnapshot: Sendable { ) } + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif init(_ snapshot: borrowing LeagueScheduleData) { entriesPerMatchup = snapshot.entriesPerMatchup entriesCount = snapshot.entriesCount diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index f3abfd6..92d7228 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -10,8 +10,10 @@ struct RedistributionData: Sendable { private var redistributedEntries:[UInt16] private(set) var redistributed:Set + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif init( dayIndex: LeagueDayIndex, startDayIndex: LeagueDayIndex, diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift index 7bea08c..b1d6620 100644 --- a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift +++ b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift @@ -69,8 +69,10 @@ struct AssignmentState: Sendable, ~Copyable { var shuffleHistory = [LeagueShuffleAction]() + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif func copyable() -> AssignmentStateCopyable { return .init( entries: entries, @@ -188,8 +190,10 @@ struct AssignmentStateCopyable { var shuffleHistory:[LeagueShuffleAction] + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif func noncopyable() -> AssignmentState { return .init( entries: entries, diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift index 12ed090..79adef5 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift @@ -65,154 +65,54 @@ extension LeagueRequestPayload { divisionsCount: Int, startingDayOfWeek: LeagueDayOfWeek ) async throws(LeagueError) -> LeagueGenerationResult { - switch settings.timeSlots { - case 1...64: + if gameDays <= 64 && settings.timeSlots <= 64 && settings.locations <= 64 && entries.count <= 64 { return try await generate( defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, - d: BitSet64() + c: ScheduleConfig< + BitSet64, + BitSet64, + BitSet64, + BitSet64 + >.self ) - case 1...128: + } else if gameDays <= 128 && settings.timeSlots <= 128 && settings.locations <= 128 && entries.count <= 128 { return try await generate( defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, - d: BitSet128() + c: ScheduleConfig< + BitSet128, + BitSet128, + BitSet128, + BitSet128 + >.self ) - default: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: Set() - ) - } - } - private func generate( - defaultGameGap: GameGap, - divisionsCount: Int, - startingDayOfWeek: LeagueDayOfWeek, - d: DaySet - ) async throws(LeagueError) -> LeagueGenerationResult { - switch settings.timeSlots { - case 1...64: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: BitSet64() - ) - case 1...128: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: BitSet128() - ) - default: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: Set() - ) - } - } - private func generate( - defaultGameGap: GameGap, - divisionsCount: Int, - startingDayOfWeek: LeagueDayOfWeek, - d: DaySet, - t: TimeSet - ) async throws(LeagueError) -> LeagueGenerationResult { - switch settings.locations { - case 1...64: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: t, - l: BitSet64() - ) - case 65...128: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: t, - l: BitSet128() - ) - default: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: t, - l: Set() - ) - } - } - private func generate( - defaultGameGap: GameGap, - divisionsCount: Int, - startingDayOfWeek: LeagueDayOfWeek, - d: DaySet, - t: TimeSet, - l: LocationSet - ) async throws(LeagueError) -> LeagueGenerationResult { - switch entries.count { - case 1...64: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: t, - l: l, - e: BitSet64() - ) - case 65...128: - return try await generate( - defaultGameGap: defaultGameGap, - divisionsCount: divisionsCount, - startingDayOfWeek: startingDayOfWeek, - d: d, - t: t, - l: l, - e: BitSet128() - ) - default: + } else { return try await generate( defaultGameGap: defaultGameGap, divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, - d: d, - t: t, - l: l, - e: Set() + c: ScheduleConfig< + Set, + Set, + Set, + Set + >.self ) } } } extension LeagueRequestPayload { - private func generate( + private func generate( defaultGameGap: GameGap, divisionsCount: Int, startingDayOfWeek: LeagueDayOfWeek, - d: DaySet, - t: TimeSet, - l: LocationSet, - e: EntryIDSet + c: Config.Type ) async throws(LeagueError) -> LeagueGenerationResult { - let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) + let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) var teamsForDivision = [Int](repeating: 0, count: divisionsCount) let entries = try parseEntries( divisionsCount: divisionsCount, @@ -235,7 +135,7 @@ extension LeagueRequestPayload { maximumPlayableMatchups: settings.maximumPlayableMatchups.array ) - let timesSet = TimeSet(0..>( + general: LeagueGeneralSettings.Runtime( gameGap: defaultGameGap, timeSlots: settings.timeSlots, startingTimes: settings.startingTimes.times, @@ -291,8 +191,10 @@ extension LeagueRequestPayload { } extension LeagueRequestPayload { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif private func generate( divisions: [Config.DivisionRuntime], entries: [Config.EntryRuntime], @@ -534,8 +436,10 @@ extension LeagueRequestPayload { // MARK: Parse day settings extension LeagueRequestPayload { + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif private func parseDaySettings( general: LeagueGeneralSettings.Runtime, correctMaximumPlayableMatchups: [UInt32], diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift index f126a91..cae2fea 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift @@ -154,8 +154,10 @@ extension LeagueGeneralSettings.Runtime { balancedLocations.contains(location) } + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif init( gameGap: GameGap, timeSlots: LeagueTimeIndex, diff --git a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift index 0068a6e..28dc1ce 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift @@ -28,8 +28,10 @@ extension LeagueRequestPayload { /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] let daySettings:[LeagueGeneralSettings.Runtime] + #if SpecializeScheduleConfiguration @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif init( gameDays: LeagueDayIndex, divisions: [Config.DivisionRuntime], From 0649ac14395ee87747b3a64ce0e09c238e826c14 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Sun, 8 Mar 2026 05:54:32 -0500 Subject: [PATCH 12/33] bug fix for `SelectSlotEarliestTimeAndSameLocationIfB2B` --- .../selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift index 46d65af..8180750 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift @@ -11,7 +11,7 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable ) -> LeagueAvailableSlot? { guard !playableSlots.isEmpty else { return nil } let homePlaysAtTimes = playsAtTimes[unchecked: team1] - let awayPlaysAtTimes = playsAtTimes[unchecked: team1] + let awayPlaysAtTimes = playsAtTimes[unchecked: team2] guard !(homePlaysAtTimes.isEmpty || awayPlaysAtTimes.isEmpty) else { return SelectSlotEarliestTime.select( team1: team1, From 521531ffcc6d11d09f702ec6cb40e6e3fcbd950f Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Sun, 8 Mar 2026 23:27:24 -0500 Subject: [PATCH 13/33] disable `testthroughput` unit test --- Tests/LeagueSchedulingTests/League3Tests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/LeagueSchedulingTests/League3Tests.swift b/Tests/LeagueSchedulingTests/League3Tests.swift index 04056c5..918ef8c 100644 --- a/Tests/LeagueSchedulingTests/League3Tests.swift +++ b/Tests/LeagueSchedulingTests/League3Tests.swift @@ -21,7 +21,7 @@ struct League3Tests: ScheduleExpectations { return (throughput, UInt64(failed.reduce(0) { $0 + $1.value })) } - @Test(.timeLimit(.minutes(1))) + //@Test(.timeLimit(.minutes(1))) func testthroughput() async throws { try await withThrowingTaskGroup(of: (success: UInt64, fail: UInt64).self) { group in for _ in 0..<5 { From 3644943f604f28fafcdde5de66c55f97e358cb11 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 05:40:59 -0500 Subject: [PATCH 14/33] add `ProtobufCodable` package trait --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 29e79c5..66470c6 100644 --- a/Package.swift +++ b/Package.swift @@ -12,6 +12,7 @@ let package = Package( ], traits: [ .default(enabledTraits: [ + "ProtobufCodable", "UncheckedArraySubscript", "SpecializeScheduleConfiguration" ]), From 1fe86cdf0e78c5fe60c2e44ce75aa5178ae0f70c Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 05:41:49 -0500 Subject: [PATCH 15/33] bonk --- Package.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Package.swift b/Package.swift index 66470c6..a272660 100644 --- a/Package.swift +++ b/Package.swift @@ -16,6 +16,7 @@ let package = Package( "UncheckedArraySubscript", "SpecializeScheduleConfiguration" ]), + .trait(name: "ProtobufCodable"), .trait(name: "UncheckedArraySubscript"), .trait(name: "SpecializeScheduleConfiguration") ], From 31602f15548d55ff85370b8af57d0df1bb482ed1 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 07:06:42 -0500 Subject: [PATCH 16/33] resolving merge conflicts --- .../DivisionMatchupDurations.proto | 2 +- .../EntryMatchupsPerGameDay.proto | 2 +- .../ProtocolBuffers/GameDayLocations.proto | 2 +- Sources/ProtocolBuffers/GameDayTimes.proto | 2 +- Sources/ProtocolBuffers/GameGaps.proto | 2 +- Sources/ProtocolBuffers/GameLocations.proto | 2 +- Sources/ProtocolBuffers/GameTimes.proto | 2 +- Sources/ProtocolBuffers/GeneralSettings.proto | 4 +- ...ence.proto => GenerationConstraints.proto} | 19 +- .../LocationTimeExclusivities.proto | 2 +- .../LocationTravelDurationFrom.proto | 2 +- .../LocationTravelDurations.proto | 2 +- Sources/ProtocolBuffers/MatchupPair.proto | 4 +- Sources/ProtocolBuffers/RequestPayload.proto | 4 + .../LeagueGenerationData.swift | 89 ++------- .../LeagueGenerationResult.swift | 13 +- Sources/league-scheduling/Leagues.swift | 138 -------------- .../data/AssignMatchup.swift | 30 ++-- .../league-scheduling/data/AssignSlots.swift | 42 ++--- .../data/AssignSlotsB2B.swift | 8 +- .../data/BalanceHomeAway.swift | 10 +- .../data/DivisionMatchupCombinations.swift | 16 +- .../Generation.swift} | 170 +++++++----------- .../data/LeagueScheduleData.swift | 50 +++--- .../data/LeagueScheduleDataSnapshot.swift | 36 ++-- .../league-scheduling/data/MatchupBlock.swift | 50 +++--- .../data/PrioritizedMatchups.swift | 12 +- .../league-scheduling/data/Redistribute.swift | 6 +- .../data/RedistributionData.swift | 26 +-- .../data/RemainingAllocations.swift | 14 +- .../data/SelectMatchup.swift | 26 +-- Sources/league-scheduling/data/Shuffle.swift | 14 +- .../data/assignment/Assign.swift | 54 +++--- .../data/assignment/Move.swift | 22 +-- .../data/assignment/Unassign.swift | 30 ++-- .../assignmentState/AssignmentState.swift | 72 ++++---- .../ScheduleConfiguration.swift | 4 +- .../data/canPlayAt/CanPlayAt+GameGap.swift | 8 +- .../data/canPlayAt/CanPlayAtNormal.swift | 12 +- .../data/canPlayAt/CanPlayAtProtocol.swift | 4 +- .../CanPlayAtSameLocationIfB2B.swift | 8 +- .../CanPlayAtWithTravelDurations.swift | 32 ++-- .../data/selectSlot/SelectSlotB2B.swift | 16 +- .../selectSlot/SelectSlotEarliestTime.swift | 28 +-- ...SlotEarliestTimeAndSameLocationIfB2B.swift | 14 +- .../data/selectSlot/SelectSlotNormal.swift | 58 +++--- .../data/selectSlot/SelectSlotProtocol.swift | 12 +- .../DivisionMatchupDurations.pb.swift | 2 +- .../EntryMatchupsPerGameDay.pb.swift | 2 +- .../generated/GameDayLocations.pb.swift | 2 +- .../generated/GameDayTimes.pb.swift | 2 +- .../generated/GameGaps.pb.swift | 2 +- .../generated/GameLocations.pb.swift | 2 +- .../generated/GameTimes.pb.swift | 2 +- .../generated/GeneralSettings.pb.swift | 4 +- .../generated/GenerationConstraints.pb.swift | 159 ++++++++++++++++ .../LocationTimeExclusivities.pb.swift | 2 +- .../LocationTravelDurationFrom.pb.swift | 2 +- .../LocationTravelDurations.pb.swift | 2 +- .../generated/MatchupPair.pb.swift | 4 +- .../generated/RequestPayload.pb.swift | 20 ++- .../codable/AvailableSlot+Codable.swift | 21 +++ .../codable/BalanceStrictness+Codable.swift | 5 + .../Byes+Codable.swift} | 7 +- .../DaySettings+Codable.swift} | 11 +- .../DaySettingsArray+Codable.swift} | 5 +- .../generated/codable/Division+Codable.swift | 99 ++++++++++ .../DivisionMatchupDurations+Codable.swift} | 5 +- .../DivisionOpponents+Codable.swift} | 7 +- .../generated/codable/Divisions+Codable.swift | 14 ++ .../generated/codable/Entry+Codable.swift | 71 ++++++++ .../EntryHomeLocations+Codable.swift} | 7 +- .../EntryMatchupsPerGameDay+Codable.swift} | 12 +- .../GameDayLocations+Codable.swift} | 10 +- .../GameDayTimes+Codable.swift} | 14 +- .../generated/codable/GameDays+Codable.swift | 14 ++ .../GameGaps+Codable.swift} | 5 +- .../GameLocations+Codable.swift} | 10 +- .../GameTimes+Codable.swift} | 10 +- .../GeneralSettings+Codable.swift} | 106 ++--------- .../GenerationConstraints+Codable.swift | 43 +++++ .../LocationTimeExclusivities+Codable.swift | 14 ++ .../LocationTimeExclusivity+Codable.swift | 14 ++ .../LocationTravelDurationFrom+Codable.swift} | 8 +- .../LocationTravelDurations+Codable.swift | 14 ++ .../generated/codable/Matchup+Codable.swift | 27 +++ .../codable/MatchupPair+Codable.swift | 21 +++ .../RedistributionSettings+Codable.swift | 29 +++ .../RequestPayload+Codable.swift} | 39 +--- .../StaticTimes+Codable.swift} | 16 +- .../codable/UInt32Array+Codable.swift | 14 ++ .../extensions/AvailableSlot+Extensions.swift | 30 +--- .../BalanceStrictness+Extensions.swift | 8 +- .../extensions/Division+Extensions.swift | 103 +---------- .../extensions/Divisions+Extensions.swift | 18 +- .../extensions/Entry+Extensions.swift | 79 +------- .../EntryMatchupsPerGameDay+Extensions.swift | 6 + .../GameDayLocations+Extensions.swift | 8 + .../extensions/GameDayTimes+Extensions.swift | 8 + .../extensions/GameDays+Extensions.swift | 18 +- .../GeneralSettings+Extensions.swift | 77 ++++++++ .../LeagueGameDayLocations+Extensions.swift | 22 --- .../LeagueGameLocations+Extensions.swift | 21 --- .../extensions/LeagueMatchup+Extensions.swift | 57 ------ .../LeagueMatchupOccurrence+Extensions.swift | 17 -- .../extensions/Matchup+Extensions.swift | 30 ++++ .../extensions/MatchupPair+Extensions.swift | 28 +-- .../RedistributionSettings+Extensions.swift | 37 +--- .../RequestPayload+Extensions.swift | 27 +++ ...te.swift => RequestPayload+Generate.swift} | 130 +++++++------- ...te.swift => RequestPayload+Validate.swift} | 6 +- ...ns.swift => SettingFlags+Extensions.swift} | 5 +- .../extensions/StaticTimes+Extensions.swift | 14 -- .../extensions/UInt32Array+Extensions.swift | 14 -- ...n+Runtime.swift => Division+Runtime.swift} | 24 +-- ...ntry+Runtime.swift => Entry+Runtime.swift} | 28 +-- ...me.swift => GeneralSettings+Runtime.swift} | 60 +++---- ... => GeneralSettings+RuntimeProtocol.swift} | 12 +- .../LeagueRequestPayload+Runtime.swift | 57 ------ .../runtime/RequestPayload+Runtime.swift | 52 ++++++ Sources/league-scheduling/globals.swift | 40 +++++ Sources/league-scheduling/typealiases.swift | 86 +++++++++ .../league-scheduling/util/AbstractSet.swift | 26 +-- .../league-scheduling/util/BitSet128.swift | 14 +- Sources/league-scheduling/util/BitSet64.swift | 14 +- .../util/EntryAssignmentData.swift | 36 ++-- .../util/ExecutionStep.swift | 4 +- Sources/league-scheduling/util/GameGap.swift | 8 +- .../league-scheduling/util/LeagueError.swift | 12 +- .../util/LeagueShuffleAction.swift | 8 +- .../BalanceNumberCalculation.swift | 4 +- .../CanPlayAtTests.swift | 24 +-- .../LeagueHTMLFormTests.swift | 14 +- .../MatchupBlockTests.swift | 2 +- .../LeagueSchedulingTests/ProtobufTests.swift | 4 +- .../schedules/ScheduleBack2Back.swift | 30 ++-- .../schedules/ScheduleBaseball.swift | 8 +- .../schedules/ScheduleBeanBagToss.swift | 90 +++++----- .../schedules/ScheduleFootball.swift | 4 +- .../schedules/ScheduleMisc.swift | 32 ++-- .../schedules/ScheduleRedistribution.swift | 16 +- .../schedules/ScheduleSameLocationIfB2B.swift | 18 +- .../schedules/ScheduleSoftball.swift | 12 +- .../schedules/ScheduleTball.swift | 4 +- .../schedules/ScheduleVolleyball.swift | 20 +-- .../BalanceHomeAwayExpectations.swift | 10 +- .../expectations/DayExpectations.swift | 2 +- .../DivisionEntryExpectations.swift | 4 +- .../expectations/ScheduleExpectations.swift | 46 ++--- .../util/MatchupsPlayedPerGameDay.swift | 4 +- .../util/ScheduleTestsProtocol.swift | 58 +++--- .../schedules/util/ValidLeagueMatchup.swift | 4 +- 152 files changed, 1907 insertions(+), 1801 deletions(-) rename Sources/ProtocolBuffers/{MatchupOccurrence.proto => GenerationConstraints.proto} (64%) delete mode 100644 Sources/league-scheduling/Leagues.swift rename Sources/league-scheduling/{LeagueSchedule.swift => data/Generation.swift} (65%) create mode 100644 Sources/league-scheduling/generated/GenerationConstraints.pb.swift create mode 100644 Sources/league-scheduling/generated/codable/AvailableSlot+Codable.swift create mode 100644 Sources/league-scheduling/generated/codable/BalanceStrictness+Codable.swift rename Sources/league-scheduling/generated/{extensions/Byes+Extensions.swift => codable/Byes+Codable.swift} (79%) rename Sources/league-scheduling/generated/{extensions/DaySettings+Extensions.swift => codable/DaySettings+Codable.swift} (68%) rename Sources/league-scheduling/generated/{extensions/DaySettingsArray+Extensions.swift => codable/DaySettingsArray+Codable.swift} (93%) create mode 100644 Sources/league-scheduling/generated/codable/Division+Codable.swift rename Sources/league-scheduling/generated/{extensions/DivisionMatchupDurations+Extensions.swift => codable/DivisionMatchupDurations+Codable.swift} (93%) rename Sources/league-scheduling/generated/{extensions/DivisionOpponents+Extensions.swift => codable/DivisionOpponents+Codable.swift} (76%) create mode 100644 Sources/league-scheduling/generated/codable/Divisions+Codable.swift create mode 100644 Sources/league-scheduling/generated/codable/Entry+Codable.swift rename Sources/league-scheduling/generated/{extensions/LeagueEntryHomeLocations+Extensions.swift => codable/EntryHomeLocations+Codable.swift} (78%) rename Sources/league-scheduling/generated/{extensions/LeagueEntryMatchupsPerGameDay+Extensions.swift => codable/EntryMatchupsPerGameDay+Codable.swift} (60%) rename Sources/league-scheduling/generated/{extensions/LocationTimeExclusivities+Extensions.swift => codable/GameDayLocations+Codable.swift} (64%) rename Sources/league-scheduling/generated/{extensions/LeagueGameDayTimes+Extensions.swift => codable/GameDayTimes+Codable.swift} (58%) create mode 100644 Sources/league-scheduling/generated/codable/GameDays+Codable.swift rename Sources/league-scheduling/generated/{extensions/GameGaps+Extensions.swift => codable/GameGaps+Codable.swift} (93%) rename Sources/league-scheduling/generated/{extensions/LocationTravelDurations+Extensions.swift => codable/GameLocations+Codable.swift} (64%) rename Sources/league-scheduling/generated/{extensions/LocationTimeExclusivity+Extensions.swift => codable/GameTimes+Codable.swift} (67%) rename Sources/league-scheduling/generated/{extensions/LeagueGeneralSettings+Extensions.swift => codable/GeneralSettings+Codable.swift} (50%) create mode 100644 Sources/league-scheduling/generated/codable/GenerationConstraints+Codable.swift create mode 100644 Sources/league-scheduling/generated/codable/LocationTimeExclusivities+Codable.swift create mode 100644 Sources/league-scheduling/generated/codable/LocationTimeExclusivity+Codable.swift rename Sources/league-scheduling/generated/{extensions/LocationTravelDurationFrom+Extensions.swift => codable/LocationTravelDurationFrom+Codable.swift} (82%) create mode 100644 Sources/league-scheduling/generated/codable/LocationTravelDurations+Codable.swift create mode 100644 Sources/league-scheduling/generated/codable/Matchup+Codable.swift create mode 100644 Sources/league-scheduling/generated/codable/MatchupPair+Codable.swift create mode 100644 Sources/league-scheduling/generated/codable/RedistributionSettings+Codable.swift rename Sources/league-scheduling/generated/{extensions/LeagueRequestPayload+Extensions.swift => codable/RequestPayload+Codable.swift} (58%) rename Sources/league-scheduling/generated/{extensions/LeagueGameTimes+Extensions.swift => codable/StaticTimes+Codable.swift} (55%) create mode 100644 Sources/league-scheduling/generated/codable/UInt32Array+Codable.swift create mode 100644 Sources/league-scheduling/generated/extensions/EntryMatchupsPerGameDay+Extensions.swift create mode 100644 Sources/league-scheduling/generated/extensions/GameDayLocations+Extensions.swift create mode 100644 Sources/league-scheduling/generated/extensions/GameDayTimes+Extensions.swift create mode 100644 Sources/league-scheduling/generated/extensions/GeneralSettings+Extensions.swift delete mode 100644 Sources/league-scheduling/generated/extensions/LeagueGameDayLocations+Extensions.swift delete mode 100644 Sources/league-scheduling/generated/extensions/LeagueGameLocations+Extensions.swift delete mode 100644 Sources/league-scheduling/generated/extensions/LeagueMatchup+Extensions.swift delete mode 100644 Sources/league-scheduling/generated/extensions/LeagueMatchupOccurrence+Extensions.swift create mode 100644 Sources/league-scheduling/generated/extensions/Matchup+Extensions.swift create mode 100644 Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift rename Sources/league-scheduling/generated/extensions/{LeagueRequestPayload+Generate.swift => RequestPayload+Generate.swift} (84%) rename Sources/league-scheduling/generated/extensions/{LeagueRequestPayload+Validate.swift => RequestPayload+Validate.swift} (96%) rename Sources/league-scheduling/generated/extensions/{LeagueSettingFlags+Extensions.swift => SettingFlags+Extensions.swift} (89%) rename Sources/league-scheduling/generated/runtime/{LeagueDivision+Runtime.swift => Division+Runtime.swift} (73%) rename Sources/league-scheduling/generated/runtime/{LeagueEntry+Runtime.swift => Entry+Runtime.swift} (83%) rename Sources/league-scheduling/generated/runtime/{LeagueGeneralSettings+Runtime.swift => GeneralSettings+Runtime.swift} (78%) rename Sources/league-scheduling/generated/runtime/{LeagueGeneralSettings+RuntimeProtocol.swift => GeneralSettings+RuntimeProtocol.swift} (86%) delete mode 100644 Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift create mode 100644 Sources/league-scheduling/generated/runtime/RequestPayload+Runtime.swift create mode 100644 Sources/league-scheduling/globals.swift create mode 100644 Sources/league-scheduling/typealiases.swift diff --git a/Sources/ProtocolBuffers/DivisionMatchupDurations.proto b/Sources/ProtocolBuffers/DivisionMatchupDurations.proto index c9a3a5e..f2f7884 100644 --- a/Sources/ProtocolBuffers/DivisionMatchupDurations.proto +++ b/Sources/ProtocolBuffers/DivisionMatchupDurations.proto @@ -31,6 +31,6 @@ syntax = "proto3"; package lit_leagues.leagues; message DivisionMatchupDurations { - // - Usage: [`LeagueDayIndex`: `matchup duration for day`] + // - Usage: [`DayIndex`: `matchup duration for day`] repeated double durations = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/EntryMatchupsPerGameDay.proto b/Sources/ProtocolBuffers/EntryMatchupsPerGameDay.proto index e61e408..bd7e02b 100644 --- a/Sources/ProtocolBuffers/EntryMatchupsPerGameDay.proto +++ b/Sources/ProtocolBuffers/EntryMatchupsPerGameDay.proto @@ -31,6 +31,6 @@ syntax = "proto3"; package lit_leagues.leagues; message EntryMatchupsPerGameDay { - // - Usage: [`LeagueDayIndex` : `maximum number of matchups the entry can play on the game day`] + // - Usage: [`DayIndex` : `maximum number of matchups the entry can play on the game day`] repeated uint32 gameDayMatchups = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/GameDayLocations.proto b/Sources/ProtocolBuffers/GameDayLocations.proto index 02b0508..d37a71b 100644 --- a/Sources/ProtocolBuffers/GameDayLocations.proto +++ b/Sources/ProtocolBuffers/GameDayLocations.proto @@ -35,6 +35,6 @@ import "GameLocations.proto"; message GameDayLocations { // Playable locations for a specific day index. // - // - Usage: [`LeagueDayIndex`: `Set`] + // - Usage: [`DayIndex`: `Set`] repeated GameLocations locations = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/GameDayTimes.proto b/Sources/ProtocolBuffers/GameDayTimes.proto index 1231073..5108fe8 100644 --- a/Sources/ProtocolBuffers/GameDayTimes.proto +++ b/Sources/ProtocolBuffers/GameDayTimes.proto @@ -35,6 +35,6 @@ import "GameTimes.proto"; message GameDayTimes { // Playable times for a specific day index. // - // - Usage: [`LeagueDayIndex`: `Set`] + // - Usage: [`DayIndex`: `Set`] repeated GameTimes times = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/GameGaps.proto b/Sources/ProtocolBuffers/GameGaps.proto index 7981f7e..a4b2134 100644 --- a/Sources/ProtocolBuffers/GameGaps.proto +++ b/Sources/ProtocolBuffers/GameGaps.proto @@ -33,6 +33,6 @@ package lit_leagues.leagues; message GameGaps { // Game gaps for specific game days. // - // - Usage: [`LeagueDayIndex`: `GameGap`] + // - Usage: [`DayIndex`: `GameGap`] repeated string gameGaps = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/GameLocations.proto b/Sources/ProtocolBuffers/GameLocations.proto index 31a8ea9..41a040c 100644 --- a/Sources/ProtocolBuffers/GameLocations.proto +++ b/Sources/ProtocolBuffers/GameLocations.proto @@ -33,6 +33,6 @@ package lit_leagues.leagues; message GameLocations { // Playable locations for a specific day index. // - // - Usage: [`LeagueDayIndex`: `Set`] + // - Usage: [`DayIndex`: `Set`] repeated uint32 locations = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/GameTimes.proto b/Sources/ProtocolBuffers/GameTimes.proto index afad38c..34d920a 100644 --- a/Sources/ProtocolBuffers/GameTimes.proto +++ b/Sources/ProtocolBuffers/GameTimes.proto @@ -33,6 +33,6 @@ package lit_leagues.leagues; message GameTimes { // Playable times for a specific day index. // - // - Usage: [`LeagueDayIndex`: `Set`] + // - Usage: [`DayIndex`: `Set`] repeated uint32 times = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/GeneralSettings.proto b/Sources/ProtocolBuffers/GeneralSettings.proto index 9fefe5a..0b4ed36 100644 --- a/Sources/ProtocolBuffers/GeneralSettings.proto +++ b/Sources/ProtocolBuffers/GeneralSettings.proto @@ -79,10 +79,10 @@ message GeneralSettings { // Number of seconds that are reserved for a matchup to play. optional double matchupDuration = 8; - // - Usage: [`LeagueLocationIndex`: `Set`] + // - Usage: [`LocationIndex`: `Set`] optional LocationTimeExclusivities locationTimeExclusivities = 9; - // - Usage: [`LeagueLocationIndex`: [`LeagueLocationIndex`: `LeagueMatchupDuration`]] + // - Usage: [`LocationIndex`: [`LocationIndex`: `MatchupDuration`]] optional LocationTravelDurations locationTravelDurations = 10; // Strictness level when balancing time allocations. diff --git a/Sources/ProtocolBuffers/MatchupOccurrence.proto b/Sources/ProtocolBuffers/GenerationConstraints.proto similarity index 64% rename from Sources/ProtocolBuffers/MatchupOccurrence.proto rename to Sources/ProtocolBuffers/GenerationConstraints.proto index f91a619..19521b1 100644 --- a/Sources/ProtocolBuffers/MatchupOccurrence.proto +++ b/Sources/ProtocolBuffers/GenerationConstraints.proto @@ -30,11 +30,18 @@ syntax = "proto3"; package lit_leagues.leagues; -// How often matchups are scheduled. -enum MatchupOccurrence { - // Matchups are scheduled to play every day. - DAILY = 0; +// Constraints that influence the schedule generation process. +message GenerationConstraints { + // Maximum number of seconds the schedule can take to generate. Default value is 60; 0=infinite (will continue until the regeneration attempt threshold is met). + optional uint32 timeoutDelay = 1; - // Matchups are scheduled to play once per week. - WEEKLY = 1; + // Maximum number of regeneration attempts when failing to generate the first game day. Default value is 100. + optional uint32 regenerationAttemptsForFirstDay = 2; + + // Maximum number of regeneration attempts when failing to generate a game day other than the first. Default value is 100. + optional uint32 regenerationAttemptsForConsecutiveDay = 3; + + // Maximum number of total regeneration attempts before stopping execution and marking the schedule generation as a failure. + // Default value is 10,000. + optional uint32 regenerationAttemptsThreshold = 4; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/LocationTimeExclusivities.proto b/Sources/ProtocolBuffers/LocationTimeExclusivities.proto index 9614e07..fe56381 100644 --- a/Sources/ProtocolBuffers/LocationTimeExclusivities.proto +++ b/Sources/ProtocolBuffers/LocationTimeExclusivities.proto @@ -33,6 +33,6 @@ package lit_leagues.leagues; import "LocationTimeExclusivity.proto"; message LocationTimeExclusivities { - // - Usage: [`LeagueLocationIndex`: `Set`] + // - Usage: [`LocationIndex`: `Set`] repeated LocationTimeExclusivity locations = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/LocationTravelDurationFrom.proto b/Sources/ProtocolBuffers/LocationTravelDurationFrom.proto index 689c7e5..991b950 100644 --- a/Sources/ProtocolBuffers/LocationTravelDurationFrom.proto +++ b/Sources/ProtocolBuffers/LocationTravelDurationFrom.proto @@ -31,6 +31,6 @@ syntax = "proto3"; package lit_leagues.leagues; message LocationTravelDurationFrom { - // - Usage: [`LeagueLocationIndex`: `LeagueMatchupDuration`] + // - Usage: [`LocationIndex`: `MatchupDuration`] repeated double travelDurationTo = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/LocationTravelDurations.proto b/Sources/ProtocolBuffers/LocationTravelDurations.proto index 9c7e743..3a30c22 100644 --- a/Sources/ProtocolBuffers/LocationTravelDurations.proto +++ b/Sources/ProtocolBuffers/LocationTravelDurations.proto @@ -33,6 +33,6 @@ package lit_leagues.leagues; import "LocationTravelDurationFrom.proto"; message LocationTravelDurations { - // - Usage: [`LeagueLocationIndex`: [`LeagueLocationIndex`: `LeagueMatchupDuration`]] + // - Usage: [`LocationIndex`: [`LocationIndex`: `MatchupDuration`]] repeated LocationTravelDurationFrom locations = 1; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/MatchupPair.proto b/Sources/ProtocolBuffers/MatchupPair.proto index bd2ca8b..5ffd24d 100644 --- a/Sources/ProtocolBuffers/MatchupPair.proto +++ b/Sources/ProtocolBuffers/MatchupPair.proto @@ -31,9 +31,9 @@ syntax = "proto3"; package lit_leagues.leagues; message MatchupPair { - // LeagueEntry.IDValue (usually the home team) + // Entry.IDValue (usually the home team) uint32 team1 = 1; - // LeagueEntry.IDValue (usually the away team) + // Entry.IDValue (usually the away team) uint32 team2 = 2; } \ No newline at end of file diff --git a/Sources/ProtocolBuffers/RequestPayload.proto b/Sources/ProtocolBuffers/RequestPayload.proto index eb8ed06..d5d4943 100644 --- a/Sources/ProtocolBuffers/RequestPayload.proto +++ b/Sources/ProtocolBuffers/RequestPayload.proto @@ -34,6 +34,7 @@ import "DaySettingsArray.proto"; import "Divisions.proto"; import "Entry.proto"; import "GeneralSettings.proto"; +import "GenerationConstraints.proto"; // The payload sent by clients to the server requesting to generate a schedule with the given settings. message RequestPayload { @@ -54,4 +55,7 @@ message RequestPayload { // The entries/teams being scheduled, including their settings. repeated Entry entries = 6; + + // Constraints that influence the schedule generation process. + optional GenerationConstraints generationConstraints = 7; } \ No newline at end of file diff --git a/Sources/league-scheduling/LeagueGenerationData.swift b/Sources/league-scheduling/LeagueGenerationData.swift index f18b284..3637044 100644 --- a/Sources/league-scheduling/LeagueGenerationData.swift +++ b/Sources/league-scheduling/LeagueGenerationData.swift @@ -1,57 +1,14 @@ public struct LeagueGenerationData: Sendable { public var error:LeagueError? = nil - public var assignLocationTimeRegenerationAttempts:LeagueRegenerationAttempt = 0 - public var negativeDayIndexRegenerationAttempts:LeagueRegenerationAttempt = 0 - public var schedule:ContiguousArray> = [] - - #if UnitTesting - /// Number of times an entry was assigned to a given time. - /// - /// - Usage: [`LeagueEntry.IDValue`: [`LeagueTimeIndex`: `amount played at LeagueTimeIndex`] - public var assignedTimes = LeagueAssignedTimes() - - /// Number of times an entry was assigned to a given location. - /// - /// - Usage: [`LeagueEntry.IDValue`: [`LeagueLocationIndex`: `amount played at LeagueLocationIndex`]] - public var assignedLocations = LeagueAssignedLocations() - - /// Number of times an entry was assigned to play at home or away against another entry. - /// - /// - Usage: [`LeagueEntry.IDValue`: [opponent `LeagueEntry.IDValue`: [`home (0) or away (1)`: `total played`]]] - public var assignedEntryHomeAways:ContiguousArray> = [] - - /// Number of times an entry was assigned to play at home or away. - /// - /// - Usage: [`LeagueEntry.IDValue`: [`home (0) or away (1)`: `total played`] ] - public var assignedHomeAways:ContiguousArray> = [] - - /// Maximum number of allocations allowed for a given entry for a given time. Zero indicates infinite. - /// - /// - Usage: [`LeagueEntry.IDValue`: [`LeagueTimeIndex`: `maximum allowed at LeagueTimeIndex`]] - public var maxTimeAllocations:ContiguousArray> = [] - - /// Maximum number of allocations allowed for a given entry for a given location. Zero indicates infinite. - /// - /// - Usage: [`LeagueEntry.IDValue`: [`LeagueLocationIndex`: `maximum allowed at LeagueLocationIndex`]] - public var maxLocationAllocations:ContiguousArray> = [] - #endif - + public var assignLocationTimeRegenerationAttempts:UInt32 = 0 + public var negativeDayIndexRegenerationAttempts:UInt32 = 0 + public var schedule:ContiguousArray> = [] public var executionSteps = [ExecutionStep]() public var shuffleHistory = [LeagueShuffleAction]() - - func scheduleSorted() -> ContiguousArray<[LeagueMatchup]> { - var array:ContiguousArray<[LeagueMatchup]> = .init(repeating: [], count: schedule.count) - for (dayIndex, matchups) in schedule.enumerated() { - array[unchecked: dayIndex] = matchups.sorted(by: { - guard $0.time == $1.time else { return $0.time < $1.time } - return $0.location < $1.location - }) - } - return array - } } +#if ProtobufCodable // MARK: Codable extension LeagueGenerationData: Codable { public func encode(to encoder: any Encoder) throws { @@ -59,37 +16,27 @@ extension LeagueGenerationData: Codable { try container.encode(assignLocationTimeRegenerationAttempts, forKey: .assignLocationTimeRegenerationAttempts) try container.encode(negativeDayIndexRegenerationAttempts, forKey: .negativeDayIndexRegenerationAttempts) try container.encode(scheduleSorted(), forKey: .schedule) - - #if UnitTesting - try container.encode(assignedTimes, forKey: .assignedTimes) - try container.encode(assignedLocations, forKey: .assignedLocations) - try container.encode(assignedEntryHomeAways, forKey: .assignedEntryHomeAways) - try container.encode(assignedHomeAways, forKey: .assignedHomeAways) - try container.encode(maxTimeAllocations, forKey: .maxTimeAllocations) - try container.encode(maxLocationAllocations, forKey: .maxLocationAllocations) - #endif - try container.encode(executionSteps, forKey: .executionSteps) try container.encode(shuffleHistory, forKey: .shuffleHistory) } -} -// MARK: CodingKeys -extension LeagueGenerationData { - public enum CodingKeys: CodingKey { + func scheduleSorted() -> ContiguousArray<[Matchup]> { + var array:ContiguousArray<[Matchup]> = .init(repeating: [], count: schedule.count) + for (dayIndex, matchups) in schedule.enumerated() { + array[unchecked: dayIndex] = matchups.sorted(by: { + guard $0.time == $1.time else { return $0.time < $1.time } + return $0.location < $1.location + }) + } + return array + } + + enum CodingKeys: CodingKey { case assignLocationTimeRegenerationAttempts case negativeDayIndexRegenerationAttempts case schedule case executionSteps case shuffleHistory - - #if UnitTesting - case assignedTimes - case assignedLocations - case assignedEntryHomeAways - case assignedHomeAways - case maxTimeAllocations - case maxLocationAllocations - #endif } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/LeagueGenerationResult.swift b/Sources/league-scheduling/LeagueGenerationResult.swift index 34bf798..9f739d6 100644 --- a/Sources/league-scheduling/LeagueGenerationResult.swift +++ b/Sources/league-scheduling/LeagueGenerationResult.swift @@ -2,16 +2,9 @@ public struct LeagueGenerationResult: Sendable { public let results:[LeagueGenerationData] public let error:String? - - init( - results: [LeagueGenerationData], - error: String? - ) { - self.results = results - self.error = error - } } -// MARK: Codable +#if ProtobufCodable extension LeagueGenerationResult: Codable { -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/Leagues.swift b/Sources/league-scheduling/Leagues.swift deleted file mode 100644 index 7d052f3..0000000 --- a/Sources/league-scheduling/Leagues.swift +++ /dev/null @@ -1,138 +0,0 @@ - -import SwiftProtobuf - -// MARK: typealiases -public typealias LeagueDayIndex = UInt32 // WARNING: do not change value type! -public typealias LeagueTimeIndex = UInt32 // WARNING: do not change value type! -public typealias LeagueLocationIndex = UInt32 // WARNING: do not change value type! -public typealias LeagueEntryMatchupsPerGameDay = UInt32 // WARNING: do not change value type! -public typealias LeagueEntriesPerMatchup = UInt32 // WARNING: do not change value type! -public typealias LeagueRegenerationAttempt = UInt16 // WARNING: do not change value type! -public typealias LeagueRecurringDayLimitInterval = UInt8 // WARNING: do not change value type! - -/// Measured in seconds. -public typealias LeagueMatchupDuration = Double // WARNING: do not change value type! -public typealias LeagueDayOfWeek = UInt8 // WARNING: do not change value type! - - -// protobufs -public typealias LeagueAvailableSlot = LitLeagues_Leagues_AvailableSlot -public typealias LeagueBalanceStrictness = LitLeagues_Leagues_BalanceStrictness -public typealias LeagueDaySettings = LitLeagues_Leagues_DaySettings -public typealias LeagueDivision = LitLeagues_Leagues_Division -public typealias LeagueEntry = LitLeagues_Leagues_Entry -public typealias LeagueGameTimes = LitLeagues_Leagues_GameTimes -public typealias LeagueGameLocations = LitLeagues_Leagues_GameLocations -//public typealias LeagueEntryMatchupsPerGameDay = LitLeagues_Leagues_EntryMatchupsPerGameDay -public typealias LeagueGeneralSettings = LitLeagues_Leagues_GeneralSettings -public typealias LeagueMatchup = LitLeagues_Leagues_Matchup -public typealias LeagueMatchupOccurrence = LitLeagues_Leagues_MatchupOccurrence -public typealias LeagueRequestPayload = LitLeagues_Leagues_RequestPayload -public typealias LeagueSettingFlags = LitLeagues_Leagues_SettingFlags -public typealias LeagueLocationTimeExclusivities = LitLeagues_Leagues_LocationTimeExclusivities -public typealias LeagueLocationTimeExclusivity = LitLeagues_Leagues_LocationTimeExclusivity -public typealias LeagueLocationTravelDurations = LitLeagues_Leagues_LocationTravelDurations -public typealias LeagueLocationTravelDurationFrom = LitLeagues_Leagues_LocationTravelDurationFrom -public typealias LeagueMatchupPair = LitLeagues_Leagues_MatchupPair - - -/// Number of times an entry was assigned to play at home or away against another entry. -/// -/// - Usage: [`LeagueEntry.IDValue`: [opponent `LeagueEntry.IDValue`: [`home (0) or away (1)`: `total played`]]] -typealias AssignedEntryHomeAways = ContiguousArray> - -/// Maximum number of times an entry can play against another entry. -/// -/// - Usage: [`LeagueEntry.IDValue`: [opponent `LeagueEntry.IDValue`: `maximum allowed matchups for opponent`]] -public typealias LeagueMaximumSameOpponentMatchups = ContiguousArray> -public typealias LeagueMaximumSameOpponentMatchupsCap = UInt32 - -/// Remaining allocations allowed for a matchup pair, for a `LeagueDayIndex`. -/// -/// - Usage: [`LeagueEntry.IDValue`: `the number of remaining allocations`] -typealias RemainingAllocations = ContiguousArray> - -/// When entries can play against each other again. -/// -/// - Usage: [`LeagueEntry.IDValue`: [opponent `LeagueEntry.IDValue`: `LeagueRecurringDayLimitInterval`]] -typealias RecurringDayLimits = ContiguousArray> - -/// Number of times an entry was assigned to a given time. -/// -/// - Usage: [`LeagueEntry.IDValue`: [`LeagueTimeIndex`: `amount played at LeagueTimeIndex`] -public typealias LeagueAssignedTimes = ContiguousArray> - -/// Number of times an entry was assigned to a given location. -/// -/// - Usage: [`LeagueEntry.IDValue`: [`LeagueLocationIndex`: `amount played at LeagueLocationIndex`]] -public typealias LeagueAssignedLocations = ContiguousArray> - -/// Maximum number of allocations allowed for a given entry for a given time. -/// -/// - Usage: [`LeagueEntry.IDValue`: [`LeagueTimeIndex`: `maximum allowed at LeagueTimeIndex`]] -typealias MaximumTimeAllocations = ContiguousArray> - -/// Maximum number of allocations allowed for a given entry for a given location. -/// -/// - Usage: [`LeagueEntry.IDValue`: [`LeagueLocationIndex`: `maximum allowed at LeagueLocationIndex`]] -typealias MaximumLocationAllocations = ContiguousArray> - -/// Slots where an entry has already played at for the `day`. -/// -/// - Usage: [`LeagueEntry.IDValue`: `Set`] -typealias PlaysAt = ContiguousArray> - -// MARK: Leagues3 -public struct Leagues3 { - public static let protobufJSONEncodingOptions: JSONEncodingOptions = { - var options = JSONEncodingOptions() - options.alwaysPrintEnumsAsInts = true - options.alwaysPrintInt64sAsNumbers = true - return options - }() - - public static let maximumAllowedRegenerationAttemptsForANegativeDayIndex:LeagueRegenerationAttempt = 100 - public static let maximumAllowedRegenerationAttemptsForASingleDay:LeagueRegenerationAttempt = 100 - public static let failedRegenerationAttemptsThreshold:LeagueRegenerationAttempt = 10_000 -} - -// MARK: global -func optimalTimeSlots( - availableTimeSlots: LeagueTimeIndex, - locations: LeagueLocationIndex, - matchupsCount: LeagueLocationIndex -) -> LeagueTimeIndex { - var totalMatchupsPlayed:LeagueLocationIndex = 0 - var filledTimes:LeagueTimeIndex = 0 - while totalMatchupsPlayed < matchupsCount { - filledTimes += 1 - totalMatchupsPlayed += locations - } - #if LOG - print("LeagueSchedule;optimalTimeSlots;availableTimeSlots=\(availableTimeSlots);locations=\(locations);matchupsCount=\(matchupsCount);totalMatchupsPlayed=\(totalMatchupsPlayed);filledTimes=\(filledTimes)") - #endif - return min(availableTimeSlots, filledTimes) -} - -func calculateAdjacentTimes( - for time: LeagueTimeIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay -) -> TimeSet { - var adjacentTimes = TimeSet() - let timeIndex = time % entryMatchupsPerGameDay - if timeIndex == 0 { - for i in 1.., + _ pair: MatchupPair, + allAvailableMatchups: Set, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup? { + ) -> Matchup? { return assignmentState.assignMatchupPair( pair, entriesCount: entriesCount, @@ -25,19 +25,19 @@ extension LeagueScheduleData { } extension AssignmentState { - /// - Returns: The `LeagueMatchup` that was successfully assigned. + /// - Returns: The `Matchup` that was successfully assigned. mutating func assignMatchupPair( - _ pair: LeagueMatchupPair, + _ pair: MatchupPair, entriesCount: Int, - entryDivisions: ContiguousArray, - day: LeagueDayIndex, + entryDivisions: ContiguousArray, + day: DayIndex, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup? { + ) -> Matchup? { var slots = playableSlots(for: pair) #if LOG let playableSlots = slots @@ -91,13 +91,13 @@ extension AssignmentState { // MARK: Playable slots extension AssignmentState { - func playableSlots(for pair: LeagueMatchupPair) -> Set { + func playableSlots(for pair: MatchupPair) -> Set { return Self.playableSlots(for: pair, remainingAllocations: remainingAllocations) } static func playableSlots( - for pair: LeagueMatchupPair, + for pair: MatchupPair, remainingAllocations: RemainingAllocations - ) -> Set { + ) -> Set { return remainingAllocations[unchecked: pair.team1].intersection(remainingAllocations[unchecked: pair.team2]) } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/AssignSlots.swift b/Sources/league-scheduling/data/AssignSlots.swift index 2bbe37b..1632a38 100644 --- a/Sources/league-scheduling/data/AssignSlots.swift +++ b/Sources/league-scheduling/data/AssignSlots.swift @@ -60,15 +60,15 @@ extension LeagueScheduleData { ) /*let isBack2Back = gameGap.min == 1 && gameGap.max == 1 && entryMatchupsPerGameDay != 1 var remainingB2BMatchupsToBeScheduled = entryMatchupsPerGameDay - var previousPrioritizedEntries = Set() - var prioritizedEntriesB2B = Set(minimumCapacity: entriesPerMatchup * locations)*/ + var previousPrioritizedEntries = Set() + var prioritizedEntriesB2B = Set(minimumCapacity: entriesPerMatchup * locations)*/ while assignmentIndex != expectedMatchupsCount { if Task.isCancelled { throw .timedOut(function: "selectAndAssignSlots") } /*combinationLoop: for combination in allowedDivisionCombinations { for (divisionIndex, divisionCombination) in combination.enumerated() { - let division = LeagueDivision.IDValue(divisionIndex) + let division = Division.IDValue(divisionIndex) let divisionMatchups = assignmentState.availableDivisionMatchups[unchecked: division] prioritizedMatchups.update(prioritizedEntries: [], availableMatchups: divisionMatchups) for matchupBlockCount in divisionCombination { @@ -127,22 +127,22 @@ extension LeagueScheduleData { extension LeagueScheduleData { /// Selects and assigns a matchup to an available slot. /// - /// - Returns: The successfully assigned `LeagueMatchup`. + /// - Returns: The successfully assigned `Matchup`. static func selectAndAssignMatchup( - day: LeagueDayIndex, - entriesPerMatchup: LeagueEntriesPerMatchup, + day: DayIndex, + entriesPerMatchup: EntriesPerMatchup, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, assignmentState: inout AssignmentState, - shouldSkipSelection: (LeagueMatchupPair) -> Bool, + shouldSkipSelection: (MatchupPair) -> Bool, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup? { - var pair:LeagueMatchupPair? = nil + ) -> Matchup? { + var pair:MatchupPair? = nil var prioritizedMatchups = PrioritizedMatchups( entriesCount: entriesCount, prioritizedEntries: assignmentState.prioritizedEntries, @@ -179,19 +179,19 @@ extension LeagueScheduleData { } static func selectAndAssignMatchup( - day: LeagueDayIndex, - entriesPerMatchup: LeagueEntriesPerMatchup, + day: DayIndex, + entriesPerMatchup: EntriesPerMatchup, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, assignmentState: inout AssignmentState, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup? { - var pair:LeagueMatchupPair? = nil + ) -> Matchup? { + var pair:MatchupPair? = nil var prioritizedMatchups = PrioritizedMatchups( entriesCount: entriesCount, prioritizedEntries: assignmentState.prioritizedEntries, diff --git a/Sources/league-scheduling/data/AssignSlotsB2B.swift b/Sources/league-scheduling/data/AssignSlotsB2B.swift index 4932266..52569c8 100644 --- a/Sources/league-scheduling/data/AssignSlotsB2B.swift +++ b/Sources/league-scheduling/data/AssignSlotsB2B.swift @@ -11,13 +11,13 @@ extension LeagueScheduleData { } // TODO: pick the optimal combination that should be selected? combinationLoop: for combination in allowedDivisionCombinations { - var assignedSlots = Set() - var combinationTimeAllocations:ContiguousArray> = .init( + var assignedSlots = Set() + var combinationTimeAllocations:ContiguousArray> = .init( repeating: .init(), count: combination.first?.count ?? 10 ) for (divisionIndex, divisionCombination) in combination.enumerated() { - let division = LeagueDivision.IDValue(divisionIndex) + let division = Division.IDValue(divisionIndex) let divisionMatchups = assignmentState.allDivisionMatchups[unchecked: division] assignmentState.availableMatchups = divisionMatchups assignmentState.prioritizedEntries.removeAllKeepingCapacity() @@ -34,7 +34,7 @@ extension LeagueScheduleData { #if LOG print("assignSlots;b2b;division=\(division);divisionCombination=\(divisionCombination);matchups.count=\(assignmentState.matchups.count);availableSlots=\(assignmentState.availableSlots.map({ $0.description }));remainingAllocations=\(assignmentState.remainingAllocations.map { $0.map({ $0.description }) })") #endif - var disallowedTimes = BitSet64() + var disallowedTimes = BitSet64() for (divisionCombinationIndex, amount) in divisionCombination.enumerated() { guard amount > 0 else { continue } let combinationTimeAllocation = combinationTimeAllocations[divisionCombinationIndex] diff --git a/Sources/league-scheduling/data/BalanceHomeAway.swift b/Sources/league-scheduling/data/BalanceHomeAway.swift index de0b117..71b7c4f 100644 --- a/Sources/league-scheduling/data/BalanceHomeAway.swift +++ b/Sources/league-scheduling/data/BalanceHomeAway.swift @@ -1,9 +1,9 @@ -extension LeagueMatchupPair { +extension MatchupPair { /// Balances home/away allocations, mutating `team1` (home) and `team2` (away) if necessary. #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif mutating func balanceHomeAway( assignmentState: borrowing AssignmentState @@ -28,8 +28,8 @@ extension LeagueMatchupPair { } private static func shouldPlayAtHome( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, + team1: Entry.IDValue, + team2: Entry.IDValue, homeMatchups: [UInt8], awayMatchups: [UInt8] ) -> Bool { diff --git a/Sources/league-scheduling/data/DivisionMatchupCombinations.swift b/Sources/league-scheduling/data/DivisionMatchupCombinations.swift index e5b7258..4b44860 100644 --- a/Sources/league-scheduling/data/DivisionMatchupCombinations.swift +++ b/Sources/league-scheduling/data/DivisionMatchupCombinations.swift @@ -1,10 +1,10 @@ // MARK: All combinations /// - Returns: All division matchup combinations separated by division. -/// - Usage: [`LeagueDivision.IDValue`: `division matchup combinations`] +/// - Usage: [`Division.IDValue`: `division matchup combinations`] func allDivisionMatchupCombinations( - entriesPerMatchup: LeagueEntriesPerMatchup, - locations: LeagueLocationIndex, + entriesPerMatchup: EntriesPerMatchup, + locations: LocationIndex, entryCountsForDivision: ContiguousArray ) -> ContiguousArray>> { var combinations:ContiguousArray>> = .init(repeating: [], count: entryCountsForDivision.count) @@ -35,10 +35,10 @@ func allDivisionMatchupCombinations( // MARK: Allowed combinations /// - Returns: Allowed division matchup combinations -/// - Usage: [`allowed matchup combination index`: [`LeagueDivision.IDValue`: `division matchup combination`]] +/// - Usage: [`allowed matchup combination index`: [`Division.IDValue`: `division matchup combination`]] func allowedDivisionMatchupCombinations( - entriesPerMatchup: LeagueEntriesPerMatchup, - locations: LeagueLocationIndex, + entriesPerMatchup: EntriesPerMatchup, + locations: LocationIndex, entryCountsForDivision: ContiguousArray ) -> ContiguousArray>> { let allCombinations = allDivisionMatchupCombinations( @@ -63,8 +63,8 @@ func allowedDivisionMatchupCombinations( } private func yieldAllowedCombinations( allCombinations: ContiguousArray>>, - division: LeagueDivision.IDValue, - locations: LeagueLocationIndex, + division: Division.IDValue, + locations: LocationIndex, results: ContiguousArray, combinationBuilder: ContiguousArray>, yield: (_ combination: ContiguousArray>) -> Void diff --git a/Sources/league-scheduling/LeagueSchedule.swift b/Sources/league-scheduling/data/Generation.swift similarity index 65% rename from Sources/league-scheduling/LeagueSchedule.swift rename to Sources/league-scheduling/data/Generation.swift index d8828c4..157710e 100644 --- a/Sources/league-scheduling/LeagueSchedule.swift +++ b/Sources/league-scheduling/data/Generation.swift @@ -6,18 +6,12 @@ import Foundation #endif // TODO: support divisions on the same day with different times -enum LeagueSchedule: Sendable, ~Copyable { - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif - static func generate( - _ settings: borrowing LeagueRequestPayload.Runtime - ) async -> LeagueGenerationResult { +extension RequestPayload.Runtime { + func generate() async -> LeagueGenerationResult { var err:String? = nil var results = [LeagueGenerationData]() do { - results = try await generateSchedules(settings: settings) + results = try await generateSchedules() for result in results { if let error = result.error { if err == nil { @@ -38,67 +32,61 @@ enum LeagueSchedule: Sendable, ~Copyable { } // MARK: Generate schedules -extension LeagueSchedule { - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif - static func generateSchedules( - settings: borrowing LeagueRequestPayload.Runtime - ) async throws -> [LeagueGenerationData] { - let divisionsCount = settings.divisions.count +extension RequestPayload.Runtime { + func generateSchedules() async throws -> [LeagueGenerationData] { + let divisionsCount = divisions.count var divisionEntries:ContiguousArray = .init(repeating: .init(), count: divisionsCount) #if LOG - print("LeagueSchedule;generateSchedules;divisionsCount=\(divisionsCount);settings.entries.count=\(settings.entries.count)") + print("LeagueSchedule;generateSchedules;divisionsCount=\(divisionsCount);entries.count=\(entries.count)") #endif - for entryIndex in 0.. maxStartingTimes { - maxStartingTimes = LeagueTimeIndex(setting.timeSlots) + maxStartingTimes = TimeIndex(setting.timeSlots) } if setting.locations > maxLocations { maxLocations = setting.locations } } - let (maxSameOpponentMatchups, _):(LeagueMaximumSameOpponentMatchups, Config?) = Self.maximumSameOpponentMatchups( - gameDays: settings.gameDays, - entriesCount: settings.entries.count, + let maxSameOpponentMatchups:MaximumSameOpponentMatchups = Self.maximumSameOpponentMatchups( + gameDays: gameDays, + entriesCount: entries.count, divisionEntries: divisionEntries, - divisions: settings.divisions + divisions: divisions ) let dataSnapshot = LeagueScheduleDataSnapshot( maxStartingTimes: maxStartingTimes, - startingTimes: settings.general.startingTimes, + startingTimes: general.startingTimes, maxLocations: maxLocations, - entriesPerMatchup: settings.general.entriesPerLocation, - maximumPlayableMatchups: settings.general.maximumPlayableMatchups, - entries: settings.entries, + entriesPerMatchup: general.entriesPerLocation, + maximumPlayableMatchups: general.maximumPlayableMatchups, + entries: entries, divisionEntries: divisionEntries, - matchupDuration: settings.general.matchupDuration, - gameGap: settings.general.gameGap.minMax, - sameLocationIfB2B: settings.general.sameLocationIfB2B, - locationTravelDurations: settings.general.locationTravelDurations ?? .init(repeating: .init(repeating: 0, count: maxLocations), count: maxLocations), + matchupDuration: general.matchupDuration, + gameGap: general.gameGap.minMax, + sameLocationIfB2B: general.sameLocationIfB2B, + locationTravelDurations: general.locationTravelDurations ?? .init(repeating: .init(repeating: 0, count: maxLocations), count: maxLocations), maxSameOpponentMatchups: maxSameOpponentMatchups ) - var grouped = [LeagueDayOfWeek:Config.EntryIDSet]() - for (divisionID, division) in settings.divisions.enumerated() { - grouped[LeagueDayOfWeek(division.dayOfWeek), default: .init()].formUnion(divisionEntries[divisionID]) + var grouped = [DayOfWeek:Config.EntryIDSet]() + for (divisionID, division) in divisions.enumerated() { + grouped[DayOfWeek(division.dayOfWeek), default: .init()].formUnion(divisionEntries[divisionID]) } let finalMaxStartingTimes = maxStartingTimes let finalMaxLocations = maxLocations - return try await withTimeout( + return try await Self.withTimeout( key: "generateSchedules", resultCount: grouped.count, timeout: .seconds(60) ) { group in for (dow, scheduledEntries) in grouped { - let settingsCopy = settings.copy() + let settingsCopy = copy() group.addTask { return Self.generateSchedule( dayOfWeek: dow, @@ -116,7 +104,7 @@ extension LeagueSchedule { } // MARK: Timeout logic -extension LeagueSchedule { +extension RequestPayload.Runtime { static func withTimeout( key: String, resultCount: Int, @@ -151,18 +139,14 @@ extension LeagueSchedule { } // MARK: Generate schedule -extension LeagueSchedule { - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif - private static func generateSchedule( - dayOfWeek: LeagueDayOfWeek, - settings: borrowing LeagueRequestPayload.Runtime, +extension RequestPayload.Runtime { + private static func generateSchedule( + dayOfWeek: DayOfWeek, + settings: borrowing RequestPayload.Runtime, dataSnapshot: LeagueScheduleDataSnapshot, divisionsCount: Int, - maxStartingTimes: LeagueTimeIndex, - maxLocations: LeagueLocationIndex, + maxStartingTimes: TimeIndex, + maxLocations: LocationIndex, scheduledEntries: Config.EntryIDSet ) -> LeagueGenerationData { let gameDays = settings.gameDays @@ -184,8 +168,8 @@ extension LeagueSchedule { var snapshots = [LeagueScheduleDataSnapshot]() snapshots.reserveCapacity(gameDays) - var gameDayRegenerationAttempt:LeagueRegenerationAttempt = 0 - var day:LeagueDayIndex = 0 + var gameDayRegenerationAttempt:UInt32 = 0 + var day:DayIndex = 0 var gameDaySettingValuesCount = 0 var data = LeagueScheduleData(snapshot: dataSnapshot) while day < gameDays { @@ -222,18 +206,21 @@ extension LeagueSchedule { return generationData } if !assignedSlots { - guard generationData.assignLocationTimeRegenerationAttempts != Leagues3.failedRegenerationAttemptsThreshold else { - generationData.error = LeagueError.failedAssignment(balanceTimeStrictness: settings.general.balanceTimeStrictness) + guard generationData.assignLocationTimeRegenerationAttempts != settings.constraints.regenerationAttemptsThreshold else { + generationData.error = .failedAssignment( + regenerationAttemptsThreshold: settings.constraints.regenerationAttemptsThreshold, + balanceTimeStrictness: settings.general.balanceTimeStrictness + ) finalizeGenerationData(generationData: &generationData, data: data) return generationData } generationData.assignLocationTimeRegenerationAttempts += 1 generationData.schedule[unchecked: day].removeAll(keepingCapacity: true) gameDayRegenerationAttempt += 1 - if gameDayRegenerationAttempt == Leagues3.maximumAllowedRegenerationAttemptsForASingleDay { + if gameDayRegenerationAttempt == settings.constraints.regenerationAttemptsForConsecutiveDay { if day == 0 { - guard generationData.negativeDayIndexRegenerationAttempts != Leagues3.maximumAllowedRegenerationAttemptsForANegativeDayIndex else { - generationData.error = LeagueError.failedNegativeDayIndex + guard generationData.negativeDayIndexRegenerationAttempts != settings.constraints.regenerationAttemptsForFirstDay else { + generationData.error = .failedNegativeDayIndex finalizeGenerationData(generationData: &generationData, data: data) return generationData } @@ -267,49 +254,32 @@ extension LeagueSchedule { return generationData } - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif - private static func finalizeGenerationData( + private static func finalizeGenerationData( generationData: inout LeagueGenerationData, data: borrowing LeagueScheduleData ) { - #if UnitTesting - generationData.assignedTimes = data.assignmentState.assignedTimes - generationData.assignedLocations = data.assignmentState.assignedLocations - generationData.assignedEntryHomeAways = data.assignmentState.assignedEntryHomeAways - //generationData.assignedHomeAways = data.assignmentState.assignedHomeAways - generationData.maxTimeAllocations = data.assignmentState.maxTimeAllocations - generationData.maxLocationAllocations = data.assignmentState.maxLocationAllocations - #endif - generationData.executionSteps = data.executionSteps generationData.shuffleHistory = data.shuffleHistory } } // MARK: Load max allocations -extension LeagueSchedule { - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif - static func loadMaxAllocations( +extension RequestPayload.Runtime { + static func loadMaxAllocations( dataSnapshot: inout LeagueScheduleDataSnapshot, gameDayDivisionEntries: inout ContiguousArray>, - settings: borrowing LeagueRequestPayload.Runtime, - maxStartingTimes: LeagueTimeIndex, - maxLocations: LeagueLocationIndex, + settings: borrowing RequestPayload.Runtime, + maxStartingTimes: TimeIndex, + maxLocations: LocationIndex, scheduledEntries: Config.EntryIDSet ) { scheduledEntries.forEach { entryIndex in let entry = settings.entries[unchecked: entryIndex] - var maxPossiblePlayed:LeagueEntryMatchupsPerGameDay = 0 + var maxPossiblePlayed:EntryMatchupsPerGameDay = 0 var maxStartingTimesPlayedAt = 0 var maxLocationsPlayedAt = 0 - //var maxPossiblePlayedForTimes = [LeagueTimeIndex](repeating: 0, count: maxStartingTimes) - //var maxPossiblePlayedForLocations = [LeagueLocationIndex](repeating: 0, count: maxLocations) + //var maxPossiblePlayedForTimes = [TimeIndex](repeating: 0, count: maxStartingTimes) + //var maxPossiblePlayedForLocations = [LocationIndex](repeating: 0, count: maxLocations) for day in 0..( totalMatchupsPlayed: some FixedWidthInteger, value: some FixedWidthInteger, - strictness: LeagueBalanceStrictness + strictness: BalanceStrictness ) -> T { guard strictness != .lenient else { return .max } var minimumValue = T(ceil(Double(totalMatchupsPlayed) / Double(value))) @@ -410,18 +380,14 @@ extension LeagueSchedule { } // MARK: Maximum same opponent matchups -extension LeagueSchedule { - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif - static func maximumSameOpponentMatchups( - gameDays: LeagueDayIndex, +extension RequestPayload.Runtime { + static func maximumSameOpponentMatchups( + gameDays: DayIndex, entriesCount: Int, divisionEntries: ContiguousArray, divisions: [Config.DivisionRuntime] - ) -> (LeagueMaximumSameOpponentMatchups, Config?) { - var maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchups = .init(repeating: .init(repeating: .max, count: entriesCount), count: entriesCount) + ) -> MaximumSameOpponentMatchups { + var maxSameOpponentMatchups:MaximumSameOpponentMatchups = .init(repeating: .init(repeating: .max, count: entriesCount), count: entriesCount) for (divisionIndex, division) in divisions.enumerated() { let divisionEntries = divisionEntries[divisionIndex] let cap = division.maxSameOpponentMatchups @@ -431,6 +397,6 @@ extension LeagueSchedule { } } } - return (maxSameOpponentMatchups, nil) + return maxSameOpponentMatchups } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index c3109e0..4314e75 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -5,30 +5,30 @@ import StaticDateTimes /// Fundamental building block that keeps track of and enforces assignment rules when building the schedule. struct LeagueScheduleData: Sendable, ~Copyable { let clock = ContinuousClock() - let entriesPerMatchup:LeagueEntriesPerMatchup + let entriesPerMatchup:EntriesPerMatchup let entriesCount:Int - let entryDivisions:ContiguousArray + let entryDivisions:ContiguousArray var expectedMatchupsCount:Int = 0 - var divisionRecurringDayLimitInterval:ContiguousArray + var divisionRecurringDayLimitInterval:ContiguousArray /// Day index that is currently being scheduled. - private(set) var day:LeagueDayIndex + private(set) var day:DayIndex /// Number of locations currently available. - //private(set) var locations:LeagueLocationIndex + //private(set) var locations:LocationIndex /// Maximum number of times a single team can play on `day`. - private(set) var defaultMaxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay + private(set) var defaultMaxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay private(set) var gameGap:GameGap.TupleValue private(set) var sameLocationIfB2B:Bool - /// - Usage: [`combination index`: [`LeagueDivision.IDValue`: [`combination`]]] + /// - Usage: [`combination index`: [`Division.IDValue`: [`combination`]]] var allowedDivisionCombinations:ContiguousArray>> = [] /// - Usage: [`selection index` : `Set`] - var failedMatchupSelections:ContiguousArray> + var failedMatchupSelections:ContiguousArray> var assignmentState:AssignmentState var prioritizeEarlierTimes:Bool @@ -40,8 +40,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { var redistributedMatchups = false #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif init( snapshot: LeagueScheduleDataSnapshot @@ -67,8 +67,8 @@ struct LeagueScheduleData: Sendable, ~Copyable { // MARK: Snapshot extension LeagueScheduleData { #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif mutating func loadSnapshot(_ snapshot: LeagueScheduleDataSnapshot) { //locations = snapshot.locations @@ -86,8 +86,8 @@ extension LeagueScheduleData { } #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif func snapshot() -> LeagueScheduleDataSnapshot { return .init(self) @@ -100,18 +100,18 @@ extension LeagueScheduleData { /// /// - Parameters: /// - day: Day index that will be scheduled. - /// - divisionEntries: Division entries that play on the `day`. (`LeagueDivision.IDValue`: `Set`) + /// - divisionEntries: Division entries that play on the `day`. (`Division.IDValue`: `Set`) /// - entryMatchupsPerGameDay: Number of times a single team will play on `day`. #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif mutating func newDay( - day: LeagueDayIndex, - daySettings: LeagueGeneralSettings.Runtime, + day: DayIndex, + daySettings: GeneralSettings.Runtime, divisionEntries: ContiguousArray, - availableSlots: Set, - settings: borrowing LeagueRequestPayload.Runtime, + availableSlots: Set, + settings: borrowing RequestPayload.Runtime, generationData: inout LeagueGenerationData ) throws(LeagueError) { let now = clock.now @@ -124,7 +124,7 @@ extension LeagueScheduleData { self.prioritizeEarlierTimes = daySettings.prioritizeEarlierTimes self.gameGap = daySettings.gameGap.minMax self.sameLocationIfB2B = daySettings.sameLocationIfB2B - var availableMatchups = Set() + var availableMatchups = Set() var prioritizedEntries = Config.EntryIDSet() prioritizedEntries.reserveCapacity(entriesCount) var entryCountsForDivision:ContiguousArray = .init(repeating: 0, count: divisionEntries.count) @@ -206,8 +206,8 @@ extension LeagueScheduleData { extension LeagueScheduleData { static func recurringDayLimitInterval( entries: Int, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay - ) -> LeagueRecurringDayLimitInterval { - return LeagueRecurringDayLimitInterval(LeagueEntryMatchupsPerGameDay(entries - 1) / entryMatchupsPerGameDay) + entryMatchupsPerGameDay: EntryMatchupsPerGameDay + ) -> RecurringDayLimitInterval { + return RecurringDayLimitInterval(EntryMatchupsPerGameDay(entries - 1) / entryMatchupsPerGameDay) } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift index 2731ee2..9fd1f7c 100644 --- a/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift +++ b/Sources/league-scheduling/data/LeagueScheduleDataSnapshot.swift @@ -2,24 +2,24 @@ import StaticDateTimes struct LeagueScheduleDataSnapshot: Sendable { - let entriesPerMatchup:LeagueEntriesPerMatchup + let entriesPerMatchup:EntriesPerMatchup let entriesCount:Int - let entryDivisions:ContiguousArray + let entryDivisions:ContiguousArray - var divisionRecurringDayLimitInterval:ContiguousArray = [] + var divisionRecurringDayLimitInterval:ContiguousArray = [] /// Day index that is currently being scheduled. - private(set) var day:LeagueDayIndex = 0 + private(set) var day:DayIndex = 0 /// Maximum number of times a single team can play on `day`. - private(set) var defaultMaxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 0 + private(set) var defaultMaxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 0 private(set) var gameGap:GameGap.TupleValue private(set) var sameLocationIfB2B:Bool var allowedDivisionCombinations:ContiguousArray>> = [] /// - Usage: [`selection index` : `Set`] - var failedMatchupSelections:ContiguousArray> + var failedMatchupSelections:ContiguousArray> var assignmentState:AssignmentStateCopyable var prioritizeEarlierTimes = false @@ -28,22 +28,22 @@ struct LeagueScheduleDataSnapshot: Sendable { var shuffleHistory = [LeagueShuffleAction]() #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif init( - maxStartingTimes: LeagueTimeIndex, + maxStartingTimes: TimeIndex, startingTimes: [StaticTime], - maxLocations: LeagueLocationIndex, - entriesPerMatchup: LeagueEntriesPerMatchup, + maxLocations: LocationIndex, + entriesPerMatchup: EntriesPerMatchup, maximumPlayableMatchups: [UInt32], entries: [Config.EntryRuntime], divisionEntries: ContiguousArray, - matchupDuration: LeagueMatchupDuration, + matchupDuration: MatchupDuration, gameGap: (Int, Int), sameLocationIfB2B: Bool, - locationTravelDurations: [[LeagueMatchupDuration]], - maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups + locationTravelDurations: [[MatchupDuration]], + maxSameOpponentMatchups: MaximumSameOpponentMatchups ) { self.entriesPerMatchup = entriesPerMatchup self.entriesCount = entries.count @@ -52,11 +52,11 @@ struct LeagueScheduleDataSnapshot: Sendable { var prioritizedEntries = Config.EntryIDSet() prioritizedEntries.reserveCapacity(entriesCount) - var entryDivisions = ContiguousArray(repeating: 0, count: entriesCount) + var entryDivisions = ContiguousArray(repeating: 0, count: entriesCount) for (index, entries) in divisionEntries.enumerated() { prioritizedEntries.formUnion(entries) entries.forEach { entry in - entryDivisions[unchecked: entry] = LeagueDivision.IDValue(index) + entryDivisions[unchecked: entry] = Division.IDValue(index) } } self.entryDivisions = entryDivisions @@ -93,8 +93,8 @@ struct LeagueScheduleDataSnapshot: Sendable { } #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif init(_ snapshot: borrowing LeagueScheduleData) { entriesPerMatchup = snapshot.entriesPerMatchup diff --git a/Sources/league-scheduling/data/MatchupBlock.swift b/Sources/league-scheduling/data/MatchupBlock.swift index c3211f7..1447519 100644 --- a/Sources/league-scheduling/data/MatchupBlock.swift +++ b/Sources/league-scheduling/data/MatchupBlock.swift @@ -4,9 +4,9 @@ extension LeagueScheduleData { /// - Returns: The assigned block of matchups mutating func assignBlockOfMatchups( amount: Int, - division: LeagueDivision.IDValue, + division: Division.IDValue, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> Set? { + ) -> Set? { if gameGap.min == 1 && gameGap.max == 1 { return Self.assignBlockOfMatchups( amount: amount, @@ -45,18 +45,18 @@ extension LeagueScheduleData { /// - Returns: The assigned block of matchups static func assignBlockOfMatchups( amount: Int, - division: LeagueDivision.IDValue, - day: LeagueDayIndex, - entriesPerMatchup: LeagueEntriesPerMatchup, + division: Division.IDValue, + day: DayIndex, + entriesPerMatchup: EntriesPerMatchup, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, assignmentState: inout AssignmentState, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> Set? { + ) -> Set? { let limit = amount * entryMatchupsPerGameDay var remainingPrioritizedEntries = assignmentState.prioritizedEntries var remainingAvailableSlots = assignmentState.availableSlots @@ -127,7 +127,7 @@ extension LeagueScheduleData { // assign the last matchup let lastLocalAssignmentStateAvailableMatchups = localAssignmentState.availableMatchups let lastSelectedEntries = selectedEntries - let shouldSkipSelection:(LeagueMatchupPair) -> Bool = entryMatchupsPerGameDay % 2 == 0 ? { pair in + let shouldSkipSelection:(MatchupPair) -> Bool = entryMatchupsPerGameDay % 2 == 0 ? { pair in var targetEntries = lastSelectedEntries targetEntries.insertMember(pair.team1) targetEntries.insertMember(pair.team2) @@ -215,20 +215,20 @@ extension LeagueScheduleData { // MARK: Select and assign extension LeagueScheduleData { private static func selectAndAssignMatchup( - day: LeagueDayIndex, - entriesPerMatchup: LeagueEntriesPerMatchup, + day: DayIndex, + entriesPerMatchup: EntriesPerMatchup, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, localAssignmentState: inout AssignmentState, remainingPrioritizedEntries: inout Config.EntryIDSet, selectedEntries: inout Config.EntryIDSet, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup? { + ) -> Matchup? { guard let leagueMatchup = selectAndAssignMatchup( day: day, entriesPerMatchup: entriesPerMatchup, @@ -252,21 +252,21 @@ extension LeagueScheduleData { return leagueMatchup } private static func selectAndAssignMatchup( - day: LeagueDayIndex, - entriesPerMatchup: LeagueEntriesPerMatchup, + day: DayIndex, + entriesPerMatchup: EntriesPerMatchup, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, localAssignmentState: inout AssignmentState, - shouldSkipSelection: (LeagueMatchupPair) -> Bool, + shouldSkipSelection: (MatchupPair) -> Bool, remainingPrioritizedEntries: inout Config.EntryIDSet, selectedEntries: inout Config.EntryIDSet, selectSlot: borrowing some SelectSlotProtocol & ~Copyable, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup? { + ) -> Matchup? { guard let leagueMatchup = selectAndAssignMatchup( day: day, entriesPerMatchup: entriesPerMatchup, diff --git a/Sources/league-scheduling/data/PrioritizedMatchups.swift b/Sources/league-scheduling/data/PrioritizedMatchups.swift index 63cb494..ccea7e1 100644 --- a/Sources/league-scheduling/data/PrioritizedMatchups.swift +++ b/Sources/league-scheduling/data/PrioritizedMatchups.swift @@ -1,12 +1,12 @@ struct PrioritizedMatchups: Sendable, ~Copyable { - private(set) var matchups:Set + private(set) var matchups:Set private(set) var availableMatchupCountForEntry:ContiguousArray init( entriesCount: Int, prioritizedEntries: Config.EntryIDSet, - availableMatchups: Set + availableMatchups: Set ) { let matchups = Self.filterMatchups(prioritizedEntries: prioritizedEntries, availableMatchups: availableMatchups) var availableMatchupCountForEntry = ContiguousArray(repeating: 0, count: entriesCount) @@ -20,7 +20,7 @@ struct PrioritizedMatchups: Sendable, ~Copyable { mutating func update( prioritizedEntries: Config.EntryIDSet, - availableMatchups: Set + availableMatchups: Set ) { matchups = Self.filterMatchups(prioritizedEntries: prioritizedEntries, availableMatchups: availableMatchups) for i in availableMatchupCountForEntry.indices { @@ -33,14 +33,14 @@ struct PrioritizedMatchups: Sendable, ~Copyable { } /// Removes the specified matchup pair from `matchups`. - mutating func remove(_ matchup: LeagueMatchupPair) { + mutating func remove(_ matchup: MatchupPair) { matchups.remove(matchup) } private static func filterMatchups( prioritizedEntries: Config.EntryIDSet, - availableMatchups: Set - ) -> Set { + availableMatchups: Set + ) -> Set { if prioritizedEntries.isEmpty { return availableMatchups } diff --git a/Sources/league-scheduling/data/Redistribute.swift b/Sources/league-scheduling/data/Redistribute.swift index 1c516d2..f976894 100644 --- a/Sources/league-scheduling/data/Redistribute.swift +++ b/Sources/league-scheduling/data/Redistribute.swift @@ -2,7 +2,7 @@ // MARK: Try redistributing extension LeagueScheduleData { mutating func tryRedistributing( - settings: borrowing LeagueRequestPayload.Runtime, + settings: borrowing RequestPayload.Runtime, generationData: inout LeagueGenerationData ) throws(LeagueError) { guard day > 0 else { @@ -37,8 +37,8 @@ extension LeagueScheduleData { } mutating func tryRedistributing( - startDayIndex: LeagueDayIndex, - settings: borrowing LeagueRequestPayload.Runtime, + startDayIndex: DayIndex, + settings: borrowing RequestPayload.Runtime, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable, generationData: inout LeagueGenerationData ) throws(LeagueError) { diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index 92d7228..5d100f5 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -1,22 +1,22 @@ struct RedistributionData: Sendable { - /// The latest `LeagueDayIndex` that is allowed to redistribute matchups from. - let startDayIndex:LeagueDayIndex - let entryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay + /// The latest `DayIndex` that is allowed to redistribute matchups from. + let startDayIndex:DayIndex + let entryMatchupsPerGameDay:EntryMatchupsPerGameDay let minMatchupsRequired:Int let maxMovableMatchups:Int private var redistributedEntries:[UInt16] - private(set) var redistributed:Set + private(set) var redistributed:Set #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif init( - dayIndex: LeagueDayIndex, - startDayIndex: LeagueDayIndex, + dayIndex: DayIndex, + startDayIndex: DayIndex, settings: LitLeagues_Leagues_RedistributionSettings?, data: borrowing LeagueScheduleData ) { @@ -44,7 +44,7 @@ extension RedistributionData { mutating func redistributeMatchups( clock: ContinuousClock, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable, - day: LeagueDayIndex, + day: DayIndex, gameGap: GameGap.TupleValue, assignmentState: inout AssignmentState, executionSteps: inout [ExecutionStep], @@ -168,7 +168,7 @@ extension RedistributionData { // MARK: Calculate min max extension RedistributionData { private func calculateMinMax( - matchup: LeagueMatchup + matchup: Matchup ) -> (minimum: UInt16, maximum: UInt16) { let home = redistributedEntries[unchecked: matchup.home] let away = redistributedEntries[unchecked: matchup.away] @@ -204,8 +204,8 @@ extension RedistributionData { // MARK: Redistributable extension RedistributionData { private struct Redistributable: Hashable, Sendable { - let fromDay:LeagueDayIndex - var matchup:LeagueMatchup - let toSlot:LeagueAvailableSlot + let fromDay:DayIndex + var matchup:Matchup + let toSlot:AvailableSlot } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/RemainingAllocations.swift b/Sources/league-scheduling/data/RemainingAllocations.swift index bef65b8..25a28d8 100644 --- a/Sources/league-scheduling/data/RemainingAllocations.swift +++ b/Sources/league-scheduling/data/RemainingAllocations.swift @@ -19,7 +19,7 @@ extension AssignmentState { } private mutating func recalculateNewDayRemainingAllocations( - for pair: LeagueMatchupPair, + for pair: MatchupPair, cached: inout Config.EntryIDSet ) { recalculateNewDayRemainingAllocations( @@ -32,7 +32,7 @@ extension AssignmentState { ) } private mutating func recalculateNewDayRemainingAllocations( - for team: LeagueEntry.IDValue, + for team: Entry.IDValue, cached: inout Config.EntryIDSet ) { guard !cached.contains(team) else { return } @@ -54,7 +54,7 @@ extension AssignmentState { // MARK: All extension AssignmentState { mutating func recalculateAllRemainingAllocations( - day: LeagueDayIndex, + day: DayIndex, entriesCount: Int, gameGap: GameGap.TupleValue, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable @@ -76,8 +76,8 @@ extension AssignmentState { } private mutating func recalculateRemainingAllocations( - day: LeagueDayIndex, - for pair: LeagueMatchupPair, + day: DayIndex, + for pair: MatchupPair, cached: inout Config.EntryIDSet, gameGap: GameGap.TupleValue, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable @@ -99,8 +99,8 @@ extension AssignmentState { } private mutating func recalculateRemainingAllocations( - day: LeagueDayIndex, - for team: LeagueEntry.IDValue, + day: DayIndex, + for team: Entry.IDValue, cached: inout Config.EntryIDSet, gameGap: GameGap.TupleValue, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable diff --git a/Sources/league-scheduling/data/SelectMatchup.swift b/Sources/league-scheduling/data/SelectMatchup.swift index d56264f..fbe9c3b 100644 --- a/Sources/league-scheduling/data/SelectMatchup.swift +++ b/Sources/league-scheduling/data/SelectMatchup.swift @@ -2,7 +2,7 @@ // MARK: Select matchup extension LeagueScheduleData { /// - Returns: Matchup pair that should be prioritized to be scheduled due to how many allocations it has remaining. - func selectMatchup(prioritizedMatchups: borrowing PrioritizedMatchups) -> LeagueMatchupPair? { + func selectMatchup(prioritizedMatchups: borrowing PrioritizedMatchups) -> MatchupPair? { return assignmentState.selectMatchup(prioritizedMatchups: prioritizedMatchups) } } @@ -11,7 +11,7 @@ extension AssignmentState { /// - Returns: Matchup pair that should be prioritized to be scheduled due to how many allocations it has remaining. func selectMatchup( prioritizedMatchups: borrowing PrioritizedMatchups - ) -> LeagueMatchupPair? { + ) -> MatchupPair? { return Self.selectMatchup( prioritizedMatchups: prioritizedMatchups, numberOfAssignedMatchups: numberOfAssignedMatchups, @@ -26,7 +26,7 @@ extension AssignmentState { numberOfAssignedMatchups: [Int], recurringDayLimits: RecurringDayLimits, remainingAllocations: RemainingAllocations - ) -> LeagueMatchupPair? { + ) -> MatchupPair? { #if LOG print("SelectMatchup;selectMatchup;prioritizedMatchups.count=\(prioritizedMatchups.matchups.count);availableMatchupCountForEntry=\(prioritizedMatchups.availableMatchupCountForEntry)") #endif @@ -46,7 +46,7 @@ extension AssignmentState { // introduce a pool of matchup pairs of equal priority, and random selection, so that we don't repeat identical assignments when // - regenerating a failed day // - selecting the last matchup pair out of previous pairs of equal priority - var pool = Set() + var pool = Set() for pair in prioritizedMatchups.matchups[prioritizedMatchups.matchups.index(after: prioritizedMatchups.matchups.startIndex)...] { let (pairMinMatchupsPlayedSoFar, pairTotalMatchupsPlayedSoFar) = numberOfMatchupsPlayedSoFar(for: pair, numberOfAssignedMatchups: numberOfAssignedMatchups) guard pairMinMatchupsPlayedSoFar == selected.minMatchupsPlayedSoFar else { @@ -164,24 +164,24 @@ extension AssignmentState { extension AssignmentState { private struct SelectedMatchup: Sendable, ~Copyable { - var pair:LeagueMatchupPair + var pair:MatchupPair /// The minimum number of matchups `pair.team1` or `pair.team2` has played so far var minMatchupsPlayedSoFar:Int /// The sum of the total number of matchups `pair.team1` and `pair.team2` has played so far var totalMatchupsPlayedSoFar:Int var remainingAllocations:(min: Int, max: Int) var remainingMatchupCount:(min: Int, max: Int) - var recurringDayLimit:LeagueRecurringDayLimitInterval + var recurringDayLimit:RecurringDayLimitInterval } - private static func numberOfMatchupsPlayedSoFar(for pair: LeagueMatchupPair, numberOfAssignedMatchups: [Int]) -> (minimum: Int, total: Int) { + private static func numberOfMatchupsPlayedSoFar(for pair: MatchupPair, numberOfAssignedMatchups: [Int]) -> (minimum: Int, total: Int) { let t1 = numberOfAssignedMatchups[unchecked: pair.team1] let t2 = numberOfAssignedMatchups[unchecked: pair.team2] return (min(t1, t2), t1 + t2) } - private static func recurringDayLimit(for pair: LeagueMatchupPair, recurringDayLimits: RecurringDayLimits) -> LeagueRecurringDayLimitInterval { + private static func recurringDayLimit(for pair: MatchupPair, recurringDayLimits: RecurringDayLimits) -> RecurringDayLimitInterval { return recurringDayLimits[unchecked: pair.team1][unchecked: pair.team2] } - private static func remainingAllocations(for pair: LeagueMatchupPair, remainingAllocations: RemainingAllocations) -> (min: Int, max: Int) { + private static func remainingAllocations(for pair: MatchupPair, remainingAllocations: RemainingAllocations) -> (min: Int, max: Int) { let team1 = remainingAllocations[unchecked: pair.team1].count let team2 = remainingAllocations[unchecked: pair.team2].count return ( @@ -189,7 +189,7 @@ extension AssignmentState { max(team1, team2) ) } - private static func remainingMatchupCount(for pair: LeagueMatchupPair, _ availableMatchupCountForEntry: ContiguousArray) -> (min: Int, max: Int) { + private static func remainingMatchupCount(for pair: MatchupPair, _ availableMatchupCountForEntry: ContiguousArray) -> (min: Int, max: Int) { let team1 = availableMatchupCountForEntry[unchecked: pair.team1] let team2 = availableMatchupCountForEntry[unchecked: pair.team2] return ( @@ -198,13 +198,13 @@ extension AssignmentState { ) } private static func select( - pair: LeagueMatchupPair, + pair: MatchupPair, minMatchupsPlayedSoFar: Int, totalMatchupsPlayedSoFar: Int, - recurringDayLimit: LeagueRecurringDayLimitInterval, + recurringDayLimit: RecurringDayLimitInterval, remainingAllocations: (min: Int, max: Int), remainingMatchupCount: (min: Int, max: Int), - pool: inout Set + pool: inout Set ) -> SelectedMatchup { pool.removeAll(keepingCapacity: true) pool.insert(pair) diff --git a/Sources/league-scheduling/data/Shuffle.swift b/Sources/league-scheduling/data/Shuffle.swift index 7558cc8..2be5822 100644 --- a/Sources/league-scheduling/data/Shuffle.swift +++ b/Sources/league-scheduling/data/Shuffle.swift @@ -3,16 +3,16 @@ extension AssignmentState { /// - Returns: The slot a matchup was sucessfully moved from. mutating func shuffle( - matchup: LeagueMatchupPair, - day: LeagueDayIndex, + matchup: MatchupPair, + day: DayIndex, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueAvailableSlot? { + ) -> AvailableSlot? { // TODO: fix (can get stuck shuffling the same matchup to the same slot) let team1AllowedTimes = entries[unchecked: matchup.team1].gameTimes[unchecked: day] let team1AllowedLocations = entries[unchecked: matchup.team1].gameLocations[unchecked: day] diff --git a/Sources/league-scheduling/data/assignment/Assign.swift b/Sources/league-scheduling/data/assignment/Assign.swift index 699ac49..f866a08 100644 --- a/Sources/league-scheduling/data/assignment/Assign.swift +++ b/Sources/league-scheduling/data/assignment/Assign.swift @@ -8,10 +8,10 @@ extension LeagueScheduleData { /// - slot: The slot to assign the `matchup`. /// - Returns: The final matchup data that was assigned. mutating func assign( - matchup: LeagueMatchupPair, - to slot: LeagueAvailableSlot, + matchup: MatchupPair, + to slot: AvailableSlot, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup { + ) -> Matchup { return assignmentState.assign( matchup: matchup, to: slot, @@ -31,26 +31,26 @@ extension AssignmentState { /// - Warning: Assigns the literal pair. **DOES NOT** balance home/away. @discardableResult mutating func assign( - matchup: LeagueMatchupPair, - to slot: LeagueAvailableSlot, - day: LeagueDayIndex, + matchup: MatchupPair, + to slot: AvailableSlot, + day: DayIndex, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable - ) -> LeagueMatchup { + ) -> Matchup { prioritizedEntries.removeMember(matchup.team1) prioritizedEntries.removeMember(matchup.team2) - let home:LeagueEntry.IDValue = matchup.team1 - let away:LeagueEntry.IDValue = matchup.team2 + let home:Entry.IDValue = matchup.team1 + let away:Entry.IDValue = matchup.team2 incrementRecurringDayLimits(home: home, away: away, entryDivisions: entryDivisions, divisionRecurringDayLimitInterval: divisionRecurringDayLimitInterval) incrementAssignData(home: home, away: away, slot: slot) insertPlaysAt(home: home, away: away, slot: slot) availableSlots.remove(slot) - let leagueMatchup = LeagueMatchup( + let leagueMatchup = Matchup( time: slot.time, location: slot.location, home: home, @@ -113,9 +113,9 @@ extension AssignmentState { // MARK: Increment assigned data extension AssignmentState { mutating func incrementAssignData( - home: LeagueEntry.IDValue, - away: LeagueEntry.IDValue, - slot: LeagueAvailableSlot + home: Entry.IDValue, + away: Entry.IDValue, + slot: AvailableSlot ) { numberOfAssignedMatchups[unchecked: home] += 1 numberOfAssignedMatchups[unchecked: away] += 1 @@ -129,9 +129,9 @@ extension AssignmentState { awayMatchups[unchecked: away] += 1 } mutating func insertPlaysAt( - home: LeagueEntry.IDValue, - away: LeagueEntry.IDValue, - slot: LeagueAvailableSlot + home: Entry.IDValue, + away: Entry.IDValue, + slot: AvailableSlot ) { playsAt[unchecked: home].insert(slot) playsAt[unchecked: away].insert(slot) @@ -145,19 +145,19 @@ extension AssignmentState { // MARK: Increment RDL extension AssignmentState { mutating func incrementRecurringDayLimits( - home: LeagueEntry.IDValue, - away: LeagueEntry.IDValue, - entryDivisions: ContiguousArray, - divisionRecurringDayLimitInterval: ContiguousArray + home: Entry.IDValue, + away: Entry.IDValue, + entryDivisions: ContiguousArray, + divisionRecurringDayLimitInterval: ContiguousArray ) { Self.incrementRecurringDayLimits(home: home, away: away, entryDivisions: entryDivisions, divisionRecurringDayLimitInterval: divisionRecurringDayLimitInterval, recurringDayLimits: &recurringDayLimits) } static func incrementRecurringDayLimits( - home: LeagueEntry.IDValue, - away: LeagueEntry.IDValue, - entryDivisions: ContiguousArray, - divisionRecurringDayLimitInterval: ContiguousArray, + home: Entry.IDValue, + away: Entry.IDValue, + entryDivisions: ContiguousArray, + divisionRecurringDayLimitInterval: ContiguousArray, recurringDayLimits: inout RecurringDayLimits ) { let recurringDayLimitInterval = divisionRecurringDayLimitInterval[unchecked: entryDivisions[unchecked: home]] diff --git a/Sources/league-scheduling/data/assignment/Move.swift b/Sources/league-scheduling/data/assignment/Move.swift index 72548d0..79dcc7a 100644 --- a/Sources/league-scheduling/data/assignment/Move.swift +++ b/Sources/league-scheduling/data/assignment/Move.swift @@ -4,10 +4,10 @@ extension LeagueScheduleData { /// Moves the specified matchup to the given slot on the same day. mutating func move( - matchup: LeagueMatchup, - to slot: LeagueAvailableSlot, - day: LeagueDayIndex, - allAvailableMatchups: Set, + matchup: Matchup, + to slot: AvailableSlot, + day: DayIndex, + allAvailableMatchups: Set, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) { assignmentState.move( @@ -28,15 +28,15 @@ extension LeagueScheduleData { // MARK: AssignmentState extension AssignmentState { mutating func move( - matchup: LeagueMatchup, - to slot: LeagueAvailableSlot, - day: LeagueDayIndex, + matchup: Matchup, + to slot: AvailableSlot, + day: DayIndex, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) { #if LOG diff --git a/Sources/league-scheduling/data/assignment/Unassign.swift b/Sources/league-scheduling/data/assignment/Unassign.swift index 55fa0dc..44b4a55 100644 --- a/Sources/league-scheduling/data/assignment/Unassign.swift +++ b/Sources/league-scheduling/data/assignment/Unassign.swift @@ -2,14 +2,14 @@ // MARK: Unassign extension AssignmentState { mutating func unassign( - matchup: LeagueMatchup, - day: LeagueDayIndex, + matchup: Matchup, + day: DayIndex, entriesCount: Int, - entryDivisions: ContiguousArray, + entryDivisions: ContiguousArray, gameGap: GameGap.TupleValue, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - divisionRecurringDayLimitInterval: ContiguousArray, - allAvailableMatchups: Set, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + divisionRecurringDayLimitInterval: ContiguousArray, + allAvailableMatchups: Set, canPlayAt: borrowing some CanPlayAtProtocol & ~Copyable ) { let recurringDayLimitInterval = divisionRecurringDayLimitInterval[unchecked: entryDivisions[unchecked: matchup.home]] @@ -40,9 +40,9 @@ extension AssignmentState { // MARK: Decrement assign data extension AssignmentState { mutating func decrementAssignData( - home: LeagueEntry.IDValue, - away: LeagueEntry.IDValue, - slot: LeagueAvailableSlot + home: Entry.IDValue, + away: Entry.IDValue, + slot: AvailableSlot ) { Self.subtractClampingOverflow(number: &numberOfAssignedMatchups[unchecked: home], amount: 1) Self.subtractClampingOverflow(number: &numberOfAssignedMatchups[unchecked: away], amount: 1) @@ -66,9 +66,9 @@ extension AssignmentState { } mutating func removePlaysAt( - home: LeagueEntry.IDValue, - away: LeagueEntry.IDValue, - slot: LeagueAvailableSlot + home: Entry.IDValue, + away: Entry.IDValue, + slot: AvailableSlot ) { playsAt[unchecked: home].remove(slot) playsAt[unchecked: away].remove(slot) @@ -82,9 +82,9 @@ extension AssignmentState { // MARK: Recalculate available matchups extension AssignmentState { mutating func recalculateAvailableMatchups( - day: LeagueDayIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - allAvailableMatchups: Set + day: DayIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + allAvailableMatchups: Set ) { availableMatchups = allAvailableMatchups.filter({ guard assignedEntryHomeAways[unchecked: $0.team1][unchecked: $0.team2].sum < maxSameOpponentMatchups[unchecked: $0.team1][unchecked: $0.team2] diff --git a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift index b1d6620..89fb822 100644 --- a/Sources/league-scheduling/data/assignmentState/AssignmentState.swift +++ b/Sources/league-scheduling/data/assignmentState/AssignmentState.swift @@ -5,73 +5,73 @@ import StaticDateTimes struct AssignmentState: Sendable, ~Copyable { let entries:[Config.EntryRuntime] var startingTimes:[StaticTime] - var matchupDuration:LeagueMatchupDuration - var locationTravelDurations:[[LeagueMatchupDuration]] + var matchupDuration:MatchupDuration + var locationTravelDurations:[[MatchupDuration]] - /// - Usage: [`LeagueEntry.IDValue`: `total number of matchups played so far in the schedule`] + /// - Usage: [`Entry.IDValue`: `total number of matchups played so far in the schedule`] var numberOfAssignedMatchups:[Int] - /// Remaining allocations allowed for a matchup pair, for a `LeagueDayIndex`. + /// Remaining allocations allowed for a matchup pair, for a `DayIndex`. /// - /// - Usage: [`LeagueEntry.IDValue`: `the number of remaining allocations`] + /// - Usage: [`Entry.IDValue`: `the number of remaining allocations`] var remainingAllocations:RemainingAllocations /// When entries can play against each other again. /// - /// - Usage: [`LeagueEntry.IDValue`: [opponent `LeagueEntry.IDValue`: `LeagueRecurringDayLimitInterval`]] + /// - Usage: [`Entry.IDValue`: [opponent `Entry.IDValue`: `RecurringDayLimitInterval`]] var recurringDayLimits:RecurringDayLimits - var assignedTimes:LeagueAssignedTimes - var assignedLocations:LeagueAssignedLocations + var assignedTimes:AssignedTimes + var assignedLocations:AssignedLocations let maximumPlayableMatchups:[UInt32] let maxTimeAllocations:MaximumTimeAllocations let maxLocationAllocations:MaximumLocationAllocations /// Number of times an entry was assigned to play at home or away against another entry. /// - /// - Usage: [`LeagueEntry.IDValue`: [opponent `LeagueEntry.IDValue`: [`home (0) or away (1)`: `total played`]]] + /// - Usage: [`Entry.IDValue`: [opponent `Entry.IDValue`: [`home (0) or away (1)`: `total played`]]] var assignedEntryHomeAways:AssignedEntryHomeAways /// Total number of 'home' matchups an entry has played. /// - /// - Usage: [`LeagueEntry.IDValue`: `number of matchups played at 'home'`] + /// - Usage: [`Entry.IDValue`: `number of matchups played at 'home'`] var homeMatchups:[UInt8] /// Total number of 'away' matchups an entry has played. /// - /// - Usage: [`LeagueEntry.IDValue`: `number of matchups played at 'away'`] + /// - Usage: [`Entry.IDValue`: `number of matchups played at 'away'`] var awayMatchups:[UInt8] - let maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchups + let maxSameOpponentMatchups:MaximumSameOpponentMatchups /// All matchup pairs that can be scheduled. - var allMatchups:Set + var allMatchups:Set /// All matchup pairs that can be scheduled, grouped by division. /// - /// - Usage: [`LeagueDivision.IDValue`: `available matchups`] - var allDivisionMatchups:ContiguousArray> + /// - Usage: [`Division.IDValue`: `available matchups`] + var allDivisionMatchups:ContiguousArray> /// Remaining available matchup pairs that can be assigned for the `day`. - var availableMatchups:Set + var availableMatchups:Set var prioritizedEntries:Config.EntryIDSet /// Remaining available slots that can be filled for the `day`. - var availableSlots:Set + var availableSlots:Set var playsAt:PlaysAt var playsAtTimes:ContiguousArray var playsAtLocations:ContiguousArray /// Available matchups that can be scheduled. - var matchups:Set + var matchups:Set var shuffleHistory = [LeagueShuffleAction]() #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif func copyable() -> AssignmentStateCopyable { return .init( @@ -140,15 +140,15 @@ struct AssignmentState: Sendable, ~Copyable { struct AssignmentStateCopyable { let entries:[Config.EntryRuntime] let startingTimes:[StaticTime] - let matchupDuration:LeagueMatchupDuration - let locationTravelDurations:[[LeagueMatchupDuration]] + let matchupDuration:MatchupDuration + let locationTravelDurations:[[MatchupDuration]] - /// - Usage: [`LeagueEntry.IDValue`: `total number of matchups played so far in the schedule`] + /// - Usage: [`Entry.IDValue`: `total number of matchups played so far in the schedule`] var numberOfAssignedMatchups:[Int] var remainingAllocations:RemainingAllocations var recurringDayLimits:RecurringDayLimits - var assignedTimes:LeagueAssignedTimes - var assignedLocations:LeagueAssignedLocations + var assignedTimes:AssignedTimes + var assignedLocations:AssignedLocations var maximumPlayableMatchups:[UInt32] var maxTimeAllocations:MaximumTimeAllocations var maxLocationAllocations:MaximumLocationAllocations @@ -157,42 +157,42 @@ struct AssignmentStateCopyable { /// Total number of 'home' matchups an entry has played. /// - /// - Usage: [`LeagueEntry.IDValue`: `# of matchups played at 'home'`] + /// - Usage: [`Entry.IDValue`: `# of matchups played at 'home'`] var homeMatchups:[UInt8] /// Total number of 'away' matchups an entry has played. /// - /// - Usage: [`LeagueEntry.IDValue`: `# of matchups played at 'away'`] + /// - Usage: [`Entry.IDValue`: `# of matchups played at 'away'`] var awayMatchups:[UInt8] - var maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchups + var maxSameOpponentMatchups:MaximumSameOpponentMatchups /// All matchup pairs that can be scheduled - var allMatchups:Set + var allMatchups:Set /// All matchup pairs that can be scheduled, grouped by division. /// - /// - Usage: [`LeagueDivision.IDValue`: `available matchups`] - var allDivisionMatchups:ContiguousArray> + /// - Usage: [`Division.IDValue`: `available matchups`] + var allDivisionMatchups:ContiguousArray> /// Remaining available matchup pairs that can be assigned for the `day`. - var availableMatchups:Set + var availableMatchups:Set var prioritizedEntries:Config.EntryIDSet /// Remaining available slots that can be filled for the `day`. - var availableSlots:Set + var availableSlots:Set var playsAt:PlaysAt var playsAtTimes:ContiguousArray var playsAtLocations:ContiguousArray - var matchups:Set + var matchups:Set var shuffleHistory:[LeagueShuffleAction] #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif func noncopyable() -> AssignmentState { return .init( diff --git a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift index 3272f21..ebeb027 100644 --- a/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift +++ b/Sources/league-scheduling/data/assignmentState/ScheduleConfiguration.swift @@ -5,8 +5,8 @@ protocol ScheduleConfiguration: Sendable, ~Copyable { associatedtype LocationSet:SetOfLocationIndexes associatedtype EntryIDSet:SetOfEntryIDs - typealias DivisionRuntime = LeagueDivision.Runtime - typealias EntryRuntime = LeagueEntry.Runtime + typealias DivisionRuntime = Division.Runtime + typealias EntryRuntime = Entry.Runtime } enum ScheduleConfig< diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift index 5f4719c..693e6d0 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAt+GameGap.swift @@ -4,15 +4,15 @@ import StaticDateTimes struct CanPlayAtGameGap: Sendable, ~Copyable { /// - Returns: If a team with the provided `playsAtTimes` can play at the given `time` taking into account a `gameGap`. static func test( - time: LeagueTimeIndex, + time: TimeIndex, playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, gameGap: GameGap.TupleValue ) -> Bool { - var closest:LeagueTimeIndex? = nil + var closest:TimeIndex? = nil playsAtTimes.forEach { playedTime in let distance = abs(playedTime.distance(to: time)) if closest == nil || distance < closest! { - closest = LeagueTimeIndex(distance) + closest = TimeIndex(distance) } } if let distance = closest { @@ -23,7 +23,7 @@ struct CanPlayAtGameGap: Sendable, ~Copyable { /// - Returns: If a game gap allows a matchup to be played given the absolute distance from the closest played time to a schedule time. static func gameGapIsAllowed( - distance: LeagueTimeIndex, + distance: TimeIndex, gameGap: GameGap.TupleValue ) -> Bool { return distance >= gameGap.min && distance <= gameGap.max diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift index 47cc51a..bdde36c 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtNormal.swift @@ -5,8 +5,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { /// - Returns: If a team with the provided data can play at the given `time` and `location`. /// - Warning: Only checks if the allocations and game gap are allowed. func test( - time: LeagueTimeIndex, - location: LeagueLocationIndex, + time: TimeIndex, + location: LocationIndex, allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, @@ -35,8 +35,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { /// - Returns: If a team with the provided data can play at the given `time` and `location`. /// - Warning: Only checks if the allocations and `gameGap` are allowed. static func test( - time: LeagueTimeIndex, - location: LeagueLocationIndex, + time: TimeIndex, + location: LocationIndex, allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, @@ -62,8 +62,8 @@ struct CanPlayAtNormal: CanPlayAtProtocol, ~Copyable { /// - Returns: If a team with the provided data can play at the given `time` and `location`. static func isAllowed( - time: LeagueTimeIndex, - location: LeagueLocationIndex, + time: TimeIndex, + location: LocationIndex, allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift index 37c5fc2..a8fc85b 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtProtocol.swift @@ -4,8 +4,8 @@ import StaticDateTimes /// Optimized storage that tests whether a team can play at a given `time` and `location` based on its current assignment data and `gameGap`. protocol CanPlayAtProtocol: Sendable, ~Copyable { func test( - time: LeagueTimeIndex, - location: LeagueLocationIndex, + time: TimeIndex, + location: LocationIndex, allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift index 1b72593..cc54cb2 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtSameLocationIfB2B.swift @@ -3,8 +3,8 @@ import StaticDateTimes struct CanPlayAtSameLocationIfB2B: CanPlayAtProtocol, ~Copyable { func test( - time: LeagueTimeIndex, - location: LeagueLocationIndex, + time: TimeIndex, + location: LocationIndex, allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, @@ -38,8 +38,8 @@ struct CanPlayAtSameLocationIfB2B: CanPlayAtProtocol, ~Copyable { /// - Returns: If a team with the provided data can play at the given `time` and `location`. static func test( - time: LeagueTimeIndex, - location: LeagueLocationIndex, + time: TimeIndex, + location: LocationIndex, playsAtTimes: borrowing some SetOfTimeIndexes & ~Copyable, playsAtLocations: borrowing some SetOfLocationIndexes & ~Copyable ) -> Bool { diff --git a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift index 6215e6b..c0e8135 100644 --- a/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift +++ b/Sources/league-scheduling/data/canPlayAt/CanPlayAtWithTravelDurations.swift @@ -3,12 +3,12 @@ import StaticDateTimes struct CanPlayAtWithTravelDurations: CanPlayAtProtocol, ~Copyable { let startingTimes:[StaticTime] - let matchupDuration:LeagueMatchupDuration - let travelDurations:[[LeagueMatchupDuration]] + let matchupDuration:MatchupDuration + let travelDurations:[[MatchupDuration]] func test( - time: LeagueTimeIndex, - location: LeagueLocationIndex, + time: TimeIndex, + location: LocationIndex, allowedTimes: borrowing some SetOfTimeIndexes & ~Copyable, allowedLocations: borrowing some SetOfLocationIndexes & ~Copyable, playsAt: PlaysAt.Element, @@ -46,20 +46,20 @@ extension CanPlayAtWithTravelDurations { /// - Returns: If a matchup can play at the given `time` and `location` taking into account the provided data. static func test( startingTimes: [StaticTime], - matchupDuration: LeagueMatchupDuration, - travelDurations: [[LeagueMatchupDuration]], - time: LeagueTimeIndex, - location: LeagueLocationIndex, + matchupDuration: MatchupDuration, + travelDurations: [[MatchupDuration]], + time: TimeIndex, + location: LocationIndex, playsAt: PlaysAt.Element, gameGap: GameGap.TupleValue ) -> Bool { - var closestSlot:LeagueAvailableSlot? = nil - var closestDistance:LeagueTimeIndex? = nil + var closestSlot:AvailableSlot? = nil + var closestDistance:TimeIndex? = nil for slot in playsAt { let distance = abs(slot.time.distance(to: time)) if closestSlot == nil || distance < closestSlot!.time { closestSlot = slot - closestDistance = LeagueTimeIndex(distance) + closestDistance = TimeIndex(distance) } } guard let closestSlot, let closestDistance else { return true } @@ -77,11 +77,11 @@ extension CanPlayAtWithTravelDurations { /// - Returns: If a matchup can play at the given `time` and `location` taking into account the provided data. static func isAllowed( startingTimes: [StaticTime], - matchupDuration: LeagueMatchupDuration, - travelDurations: [[LeagueMatchupDuration]], - closestSlot: LeagueAvailableSlot, - time: LeagueTimeIndex, - location: LeagueLocationIndex + matchupDuration: MatchupDuration, + travelDurations: [[MatchupDuration]], + closestSlot: AvailableSlot, + time: TimeIndex, + location: LocationIndex ) -> Bool { let totalDuration = matchupDuration + travelDurations[unchecked: closestSlot.location][unchecked: location] var closestTime = startingTimes[unchecked: closestSlot.time] diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift index dad05f5..03b0252 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift @@ -1,16 +1,16 @@ struct SelectSlotB2B: SelectSlotProtocol, ~Copyable { - let entryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay + let entryMatchupsPerGameDay:EntryMatchupsPerGameDay func select( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - assignedTimes: LeagueAssignedTimes, - assignedLocations: LeagueAssignedLocations, + team1: Entry.IDValue, + team2: Entry.IDValue, + assignedTimes: AssignedTimes, + assignedLocations: AssignedLocations, playsAtTimes: ContiguousArray, playsAtLocations: ContiguousArray, - playableSlots: inout Set - ) -> LeagueAvailableSlot? { + playableSlots: inout Set + ) -> AvailableSlot? { filter( team1PlaysAtTimes: playsAtTimes[unchecked: team1], team2PlaysAtTimes: playsAtTimes[unchecked: team2], @@ -31,7 +31,7 @@ extension SelectSlotB2B { private func filter( team1PlaysAtTimes: TimeSet, team2PlaysAtTimes: TimeSet, - playableSlots: inout Set + playableSlots: inout Set ) { //print("filterSlotBack2Back;playsAtTimes[unchecked: team1].isEmpty=\(playsAtTimes[unchecked: team1].isEmpty);playsAtTimes[unchecked: team2].isEmpty=\(playsAtTimes[unchecked: team2].isEmpty)") if team1PlaysAtTimes.isEmpty && team2PlaysAtTimes.isEmpty { diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift index bd62b9c..563d495 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTime.swift @@ -1,14 +1,14 @@ struct SelectSlotEarliestTime: SelectSlotProtocol, ~Copyable { func select( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - assignedTimes: LeagueAssignedTimes, - assignedLocations: LeagueAssignedLocations, + team1: Entry.IDValue, + team2: Entry.IDValue, + assignedTimes: AssignedTimes, + assignedLocations: AssignedLocations, playsAtTimes: ContiguousArray, playsAtLocations: ContiguousArray, - playableSlots: inout Set - ) -> LeagueAvailableSlot? { + playableSlots: inout Set + ) -> AvailableSlot? { return Self.select( team1: team1, team2: team2, @@ -21,12 +21,12 @@ struct SelectSlotEarliestTime: SelectSlotProtocol, ~Copyable { extension SelectSlotEarliestTime { static func select( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - assignedTimes: LeagueAssignedTimes, - assignedLocations: LeagueAssignedLocations, - playableSlots: inout Set - ) -> LeagueAvailableSlot? { + team1: Entry.IDValue, + team2: Entry.IDValue, + assignedTimes: AssignedTimes, + assignedLocations: AssignedLocations, + playableSlots: inout Set + ) -> AvailableSlot? { filter(playableSlots: &playableSlots) return SelectSlotNormal.select( team1: team1, @@ -38,8 +38,8 @@ extension SelectSlotEarliestTime { } /// Mutates `playableSlots` so it only contains the slots at the earliest available time. - static func filter(playableSlots: inout Set) { - var earliestTime = LeagueTimeIndex.max + static func filter(playableSlots: inout Set) { + var earliestTime = TimeIndex.max for slot in playableSlots { if slot.time < earliestTime { earliestTime = slot.time diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift index 8180750..1ddb62e 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotEarliestTimeAndSameLocationIfB2B.swift @@ -1,14 +1,14 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable { func select( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - assignedTimes: LeagueAssignedTimes, - assignedLocations: LeagueAssignedLocations, + team1: Entry.IDValue, + team2: Entry.IDValue, + assignedTimes: AssignedTimes, + assignedLocations: AssignedLocations, playsAtTimes: ContiguousArray, playsAtLocations: ContiguousArray, - playableSlots: inout Set - ) -> LeagueAvailableSlot? { + playableSlots: inout Set + ) -> AvailableSlot? { guard !playableSlots.isEmpty else { return nil } let homePlaysAtTimes = playsAtTimes[unchecked: team1] let awayPlaysAtTimes = playsAtTimes[unchecked: team2] @@ -30,7 +30,7 @@ struct SelectSlotEarliestTimeAndSameLocationIfB2B: SelectSlotProtocol, ~Copyable let team1PlaysAtLocations = playsAtLocations[unchecked: team1] let team2PlaysAtLocations = playsAtLocations[unchecked: team2] - var nonBackToBackSlots = [LeagueAvailableSlot]() + var nonBackToBackSlots = [AvailableSlot]() nonBackToBackSlots.reserveCapacity(playableSlots.count) // TODO: fix | balancing of home/away can make the new home no longer play at home diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift index 952c1cf..d6edb47 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotNormal.swift @@ -1,14 +1,14 @@ struct SelectSlotNormal: SelectSlotProtocol, ~Copyable { func select( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - assignedTimes: LeagueAssignedTimes, - assignedLocations: LeagueAssignedLocations, + team1: Entry.IDValue, + team2: Entry.IDValue, + assignedTimes: AssignedTimes, + assignedLocations: AssignedLocations, playsAtTimes: ContiguousArray, playsAtLocations: ContiguousArray, - playableSlots: inout Set - ) -> LeagueAvailableSlot? { + playableSlots: inout Set + ) -> AvailableSlot? { return Self.select( team1: team1, team2: team2, @@ -22,12 +22,12 @@ struct SelectSlotNormal: SelectSlotProtocol, ~Copyable { extension SelectSlotNormal { /// Selects a slot from `playableSlots` based on the least number of matchups they've already played at the given times and locations. static func select( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - assignedTimes: LeagueAssignedTimes, - assignedLocations: LeagueAssignedLocations, - playableSlots: Set - ) -> LeagueAvailableSlot? { + team1: Entry.IDValue, + team2: Entry.IDValue, + assignedTimes: AssignedTimes, + assignedLocations: AssignedLocations, + playableSlots: Set + ) -> AvailableSlot? { guard !playableSlots.isEmpty else { return nil } let team1Times = assignedTimes[unchecked: team1] let team2Times = assignedTimes[unchecked: team2] @@ -44,12 +44,12 @@ extension SelectSlotNormal { /// Selects a slot from `playableSlots` based on the least number of matchups they've already played at the given times and locations. static func select( - team1Times: LeagueAssignedTimes.Element, - team1Locations: LeagueAssignedLocations.Element, - team2Times: LeagueAssignedTimes.Element, - team2Locations: LeagueAssignedLocations.Element, - playableSlots: Set - ) -> LeagueAvailableSlot? { + team1Times: AssignedTimes.Element, + team1Locations: AssignedLocations.Element, + team2Times: AssignedTimes.Element, + team2Locations: AssignedLocations.Element, + playableSlots: Set + ) -> AvailableSlot? { var selected = getSelectedSlot(playableSlots[playableSlots.startIndex], team1Times, team1Locations, team2Times, team2Locations) for slot in playableSlots[playableSlots.index(after: playableSlots.startIndex)...] { let minimum = getMinimumAssigned(slot, team1Times, team1Locations, team2Times, team2Locations) @@ -62,20 +62,20 @@ extension SelectSlotNormal { } private static func getSelectedSlot( - _ slot: LeagueAvailableSlot, - _ team1Times: LeagueAssignedTimes.Element, - _ team1Locations: LeagueAssignedLocations.Element, - _ team2Times: LeagueAssignedTimes.Element, - _ team2Locations: LeagueAssignedLocations.Element + _ slot: AvailableSlot, + _ team1Times: AssignedTimes.Element, + _ team1Locations: AssignedLocations.Element, + _ team2Times: AssignedTimes.Element, + _ team2Locations: AssignedLocations.Element ) -> SelectedSlot { return SelectedSlot(slot: slot, minimumAssigned: getMinimumAssigned(slot, team1Times, team1Locations, team2Times, team2Locations)) } private static func getMinimumAssigned( - _ slot: LeagueAvailableSlot, - _ team1Times: LeagueAssignedTimes.Element, - _ team1Locations: LeagueAssignedLocations.Element, - _ team2Times: LeagueAssignedTimes.Element, - _ team2Locations: LeagueAssignedLocations.Element + _ slot: AvailableSlot, + _ team1Times: AssignedTimes.Element, + _ team1Locations: AssignedLocations.Element, + _ team2Times: AssignedTimes.Element, + _ team2Locations: AssignedLocations.Element ) -> UInt8 { return min( min(team1Times[unchecked: slot.time], team1Locations[unchecked: slot.location]), @@ -84,7 +84,7 @@ extension SelectSlotNormal { } struct SelectedSlot: Sendable, ~Copyable { - var slot:LeagueAvailableSlot + var slot:AvailableSlot var minimumAssigned:UInt8 } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift index 9f5971d..f7be865 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotProtocol.swift @@ -1,12 +1,12 @@ protocol SelectSlotProtocol: Sendable, ~Copyable { func select( - team1: LeagueEntry.IDValue, - team2: LeagueEntry.IDValue, - assignedTimes: LeagueAssignedTimes, - assignedLocations: LeagueAssignedLocations, + team1: Entry.IDValue, + team2: Entry.IDValue, + assignedTimes: AssignedTimes, + assignedLocations: AssignedLocations, playsAtTimes: ContiguousArray, playsAtLocations: ContiguousArray, - playableSlots: inout Set - ) -> LeagueAvailableSlot? + playableSlots: inout Set + ) -> AvailableSlot? } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/DivisionMatchupDurations.pb.swift b/Sources/league-scheduling/generated/DivisionMatchupDurations.pb.swift index 7440982..9772299 100644 --- a/Sources/league-scheduling/generated/DivisionMatchupDurations.pb.swift +++ b/Sources/league-scheduling/generated/DivisionMatchupDurations.pb.swift @@ -53,7 +53,7 @@ public struct LitLeagues_Leagues_DivisionMatchupDurations: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// - Usage: [`LeagueDayIndex`: `matchup duration for day`] + /// - Usage: [`DayIndex`: `matchup duration for day`] public var durations: [Double] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/EntryMatchupsPerGameDay.pb.swift b/Sources/league-scheduling/generated/EntryMatchupsPerGameDay.pb.swift index 9b150a1..ffacf4b 100644 --- a/Sources/league-scheduling/generated/EntryMatchupsPerGameDay.pb.swift +++ b/Sources/league-scheduling/generated/EntryMatchupsPerGameDay.pb.swift @@ -53,7 +53,7 @@ public struct LitLeagues_Leagues_EntryMatchupsPerGameDay: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// - Usage: [`LeagueDayIndex` : `maximum number of matchups the entry can play on the game day`] + /// - Usage: [`DayIndex` : `maximum number of matchups the entry can play on the game day`] public var gameDayMatchups: [UInt32] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/GameDayLocations.pb.swift b/Sources/league-scheduling/generated/GameDayLocations.pb.swift index 58ea03d..c2cc579 100644 --- a/Sources/league-scheduling/generated/GameDayLocations.pb.swift +++ b/Sources/league-scheduling/generated/GameDayLocations.pb.swift @@ -55,7 +55,7 @@ public struct LitLeagues_Leagues_GameDayLocations: Sendable { /// Playable locations for a specific day index. /// - /// - Usage: [`LeagueDayIndex`: `Set`] + /// - Usage: [`DayIndex`: `Set`] public var locations: [LitLeagues_Leagues_GameLocations] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/GameDayTimes.pb.swift b/Sources/league-scheduling/generated/GameDayTimes.pb.swift index 47f86bd..c05cabf 100644 --- a/Sources/league-scheduling/generated/GameDayTimes.pb.swift +++ b/Sources/league-scheduling/generated/GameDayTimes.pb.swift @@ -55,7 +55,7 @@ public struct LitLeagues_Leagues_GameDayTimes: Sendable { /// Playable times for a specific day index. /// - /// - Usage: [`LeagueDayIndex`: `Set`] + /// - Usage: [`DayIndex`: `Set`] public var times: [LitLeagues_Leagues_GameTimes] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/GameGaps.pb.swift b/Sources/league-scheduling/generated/GameGaps.pb.swift index 479a20f..ab620f6 100644 --- a/Sources/league-scheduling/generated/GameGaps.pb.swift +++ b/Sources/league-scheduling/generated/GameGaps.pb.swift @@ -55,7 +55,7 @@ public struct LitLeagues_Leagues_GameGaps: Sendable { /// Game gaps for specific game days. /// - /// - Usage: [`LeagueDayIndex`: `GameGap`] + /// - Usage: [`DayIndex`: `GameGap`] public var gameGaps: [String] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/GameLocations.pb.swift b/Sources/league-scheduling/generated/GameLocations.pb.swift index 3d482b9..0d196c7 100644 --- a/Sources/league-scheduling/generated/GameLocations.pb.swift +++ b/Sources/league-scheduling/generated/GameLocations.pb.swift @@ -55,7 +55,7 @@ public struct LitLeagues_Leagues_GameLocations: Sendable { /// Playable locations for a specific day index. /// - /// - Usage: [`LeagueDayIndex`: `Set`] + /// - Usage: [`DayIndex`: `Set`] public var locations: [UInt32] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/GameTimes.pb.swift b/Sources/league-scheduling/generated/GameTimes.pb.swift index 0f20aed..14b2414 100644 --- a/Sources/league-scheduling/generated/GameTimes.pb.swift +++ b/Sources/league-scheduling/generated/GameTimes.pb.swift @@ -55,7 +55,7 @@ public struct LitLeagues_Leagues_GameTimes: Sendable { /// Playable times for a specific day index. /// - /// - Usage: [`LeagueDayIndex`: `Set`] + /// - Usage: [`DayIndex`: `Set`] public var times: [UInt32] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/GeneralSettings.pb.swift b/Sources/league-scheduling/generated/GeneralSettings.pb.swift index a40be76..df9c3f9 100644 --- a/Sources/league-scheduling/generated/GeneralSettings.pb.swift +++ b/Sources/league-scheduling/generated/GeneralSettings.pb.swift @@ -150,7 +150,7 @@ public struct LitLeagues_Leagues_GeneralSettings: @unchecked Sendable { /// Clears the value of `matchupDuration`. Subsequent reads from it will return its default value. public mutating func clearMatchupDuration() {_uniqueStorage()._matchupDuration = nil} - /// - Usage: [`LeagueLocationIndex`: `Set`] + /// - Usage: [`LocationIndex`: `Set`] public var locationTimeExclusivities: LitLeagues_Leagues_LocationTimeExclusivities { get {return _storage._locationTimeExclusivities ?? LitLeagues_Leagues_LocationTimeExclusivities()} set {_uniqueStorage()._locationTimeExclusivities = newValue} @@ -160,7 +160,7 @@ public struct LitLeagues_Leagues_GeneralSettings: @unchecked Sendable { /// Clears the value of `locationTimeExclusivities`. Subsequent reads from it will return its default value. public mutating func clearLocationTimeExclusivities() {_uniqueStorage()._locationTimeExclusivities = nil} - /// - Usage: [`LeagueLocationIndex`: [`LeagueLocationIndex`: `LeagueMatchupDuration`]] + /// - Usage: [`LocationIndex`: [`LocationIndex`: `MatchupDuration`]] public var locationTravelDurations: LitLeagues_Leagues_LocationTravelDurations { get {return _storage._locationTravelDurations ?? LitLeagues_Leagues_LocationTravelDurations()} set {_uniqueStorage()._locationTravelDurations = newValue} diff --git a/Sources/league-scheduling/generated/GenerationConstraints.pb.swift b/Sources/league-scheduling/generated/GenerationConstraints.pb.swift new file mode 100644 index 0000000..7c097e9 --- /dev/null +++ b/Sources/league-scheduling/generated/GenerationConstraints.pb.swift @@ -0,0 +1,159 @@ +// DO NOT EDIT. +// swift-format-ignore-file +// swiftlint:disable all +// +// Generated by the Swift generator plugin for the protocol buffer compiler. +// Source: GenerationConstraints.proto +// +// For information on using the generated types, please see the documentation: +// https://github.com/apple/swift-protobuf/ + +// Copyright 2026 Evan Anderson. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Evan Anderson nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import SwiftProtobuf + +// If the compiler emits an error on this type, it is because this file +// was generated by a version of the `protoc` Swift plug-in that is +// incompatible with the version of SwiftProtobuf to which you are linking. +// Please ensure that you are building against the same version of the API +// that was used to generate this file. +fileprivate struct _GeneratedWithProtocGenSwiftVersion: SwiftProtobuf.ProtobufAPIVersionCheck { + struct _2: SwiftProtobuf.ProtobufAPIVersion_2 {} + typealias Version = _2 +} + +/// Constraints that influence the schedule generation process. +public struct LitLeagues_Leagues_GenerationConstraints: Sendable { + // SwiftProtobuf.Message conformance is added in an extension below. See the + // `Message` and `Message+*Additions` files in the SwiftProtobuf library for + // methods supported on all messages. + + /// Maximum number of seconds the schedule can take to generate. Default value is 60; 0=infinite (will continue until the regeneration attempt threshold is met). + public var timeoutDelay: UInt32 { + get {return _timeoutDelay ?? 0} + set {_timeoutDelay = newValue} + } + /// Returns true if `timeoutDelay` has been explicitly set. + public var hasTimeoutDelay: Bool {return self._timeoutDelay != nil} + /// Clears the value of `timeoutDelay`. Subsequent reads from it will return its default value. + public mutating func clearTimeoutDelay() {self._timeoutDelay = nil} + + /// Maximum number of regeneration attempts when failing to generate the first game day. Default value is 100. + public var regenerationAttemptsForFirstDay: UInt32 { + get {return _regenerationAttemptsForFirstDay ?? 0} + set {_regenerationAttemptsForFirstDay = newValue} + } + /// Returns true if `regenerationAttemptsForFirstDay` has been explicitly set. + public var hasRegenerationAttemptsForFirstDay: Bool {return self._regenerationAttemptsForFirstDay != nil} + /// Clears the value of `regenerationAttemptsForFirstDay`. Subsequent reads from it will return its default value. + public mutating func clearRegenerationAttemptsForFirstDay() {self._regenerationAttemptsForFirstDay = nil} + + /// Maximum number of regeneration attempts when failing to generate a game day other than the first. Default value is 100. + public var regenerationAttemptsForConsecutiveDay: UInt32 { + get {return _regenerationAttemptsForConsecutiveDay ?? 0} + set {_regenerationAttemptsForConsecutiveDay = newValue} + } + /// Returns true if `regenerationAttemptsForConsecutiveDay` has been explicitly set. + public var hasRegenerationAttemptsForConsecutiveDay: Bool {return self._regenerationAttemptsForConsecutiveDay != nil} + /// Clears the value of `regenerationAttemptsForConsecutiveDay`. Subsequent reads from it will return its default value. + public mutating func clearRegenerationAttemptsForConsecutiveDay() {self._regenerationAttemptsForConsecutiveDay = nil} + + /// Maximum number of total regeneration attempts before stopping execution and marking the schedule generation as a failure. + /// Default value is 10,000. + public var regenerationAttemptsThreshold: UInt32 { + get {return _regenerationAttemptsThreshold ?? 0} + set {_regenerationAttemptsThreshold = newValue} + } + /// Returns true if `regenerationAttemptsThreshold` has been explicitly set. + public var hasRegenerationAttemptsThreshold: Bool {return self._regenerationAttemptsThreshold != nil} + /// Clears the value of `regenerationAttemptsThreshold`. Subsequent reads from it will return its default value. + public mutating func clearRegenerationAttemptsThreshold() {self._regenerationAttemptsThreshold = nil} + + public var unknownFields = SwiftProtobuf.UnknownStorage() + + public init() {} + + fileprivate var _timeoutDelay: UInt32? = nil + fileprivate var _regenerationAttemptsForFirstDay: UInt32? = nil + fileprivate var _regenerationAttemptsForConsecutiveDay: UInt32? = nil + fileprivate var _regenerationAttemptsThreshold: UInt32? = nil +} + +// MARK: - Code below here is support for the SwiftProtobuf runtime. + +fileprivate let _protobuf_package = "lit_leagues.leagues" + +extension LitLeagues_Leagues_GenerationConstraints: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { + public static let protoMessageName: String = _protobuf_package + ".GenerationConstraints" + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}timeoutDelay\0\u{1}regenerationAttemptsForFirstDay\0\u{1}regenerationAttemptsForConsecutiveDay\0\u{1}regenerationAttemptsThreshold\0") + + public mutating func decodeMessage(decoder: inout D) throws { + while let fieldNumber = try decoder.nextFieldNumber() { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every case branch when no optimizations are + // enabled. https://github.com/apple/swift-protobuf/issues/1034 + switch fieldNumber { + case 1: try { try decoder.decodeSingularUInt32Field(value: &self._timeoutDelay) }() + case 2: try { try decoder.decodeSingularUInt32Field(value: &self._regenerationAttemptsForFirstDay) }() + case 3: try { try decoder.decodeSingularUInt32Field(value: &self._regenerationAttemptsForConsecutiveDay) }() + case 4: try { try decoder.decodeSingularUInt32Field(value: &self._regenerationAttemptsThreshold) }() + default: break + } + } + } + + public func traverse(visitor: inout V) throws { + // The use of inline closures is to circumvent an issue where the compiler + // allocates stack space for every if/case branch local when no optimizations + // are enabled. https://github.com/apple/swift-protobuf/issues/1034 and + // https://github.com/apple/swift-protobuf/issues/1182 + try { if let v = self._timeoutDelay { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 1) + } }() + try { if let v = self._regenerationAttemptsForFirstDay { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 2) + } }() + try { if let v = self._regenerationAttemptsForConsecutiveDay { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 3) + } }() + try { if let v = self._regenerationAttemptsThreshold { + try visitor.visitSingularUInt32Field(value: v, fieldNumber: 4) + } }() + try unknownFields.traverse(visitor: &visitor) + } + + public static func ==(lhs: LitLeagues_Leagues_GenerationConstraints, rhs: LitLeagues_Leagues_GenerationConstraints) -> Bool { + if lhs._timeoutDelay != rhs._timeoutDelay {return false} + if lhs._regenerationAttemptsForFirstDay != rhs._regenerationAttemptsForFirstDay {return false} + if lhs._regenerationAttemptsForConsecutiveDay != rhs._regenerationAttemptsForConsecutiveDay {return false} + if lhs._regenerationAttemptsThreshold != rhs._regenerationAttemptsThreshold {return false} + if lhs.unknownFields != rhs.unknownFields {return false} + return true + } +} diff --git a/Sources/league-scheduling/generated/LocationTimeExclusivities.pb.swift b/Sources/league-scheduling/generated/LocationTimeExclusivities.pb.swift index 250f6f7..772c5b9 100644 --- a/Sources/league-scheduling/generated/LocationTimeExclusivities.pb.swift +++ b/Sources/league-scheduling/generated/LocationTimeExclusivities.pb.swift @@ -53,7 +53,7 @@ public struct LitLeagues_Leagues_LocationTimeExclusivities: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// - Usage: [`LeagueLocationIndex`: `Set`] + /// - Usage: [`LocationIndex`: `Set`] public var locations: [LitLeagues_Leagues_LocationTimeExclusivity] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/LocationTravelDurationFrom.pb.swift b/Sources/league-scheduling/generated/LocationTravelDurationFrom.pb.swift index c89761d..e026ae5 100644 --- a/Sources/league-scheduling/generated/LocationTravelDurationFrom.pb.swift +++ b/Sources/league-scheduling/generated/LocationTravelDurationFrom.pb.swift @@ -53,7 +53,7 @@ public struct LitLeagues_Leagues_LocationTravelDurationFrom: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// - Usage: [`LeagueLocationIndex`: `LeagueMatchupDuration`] + /// - Usage: [`LocationIndex`: `MatchupDuration`] public var travelDurationTo: [Double] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/LocationTravelDurations.pb.swift b/Sources/league-scheduling/generated/LocationTravelDurations.pb.swift index ec8b6db..5947df8 100644 --- a/Sources/league-scheduling/generated/LocationTravelDurations.pb.swift +++ b/Sources/league-scheduling/generated/LocationTravelDurations.pb.swift @@ -53,7 +53,7 @@ public struct LitLeagues_Leagues_LocationTravelDurations: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// - Usage: [`LeagueLocationIndex`: [`LeagueLocationIndex`: `LeagueMatchupDuration`]] + /// - Usage: [`LocationIndex`: [`LocationIndex`: `MatchupDuration`]] public var locations: [LitLeagues_Leagues_LocationTravelDurationFrom] = [] public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/MatchupPair.pb.swift b/Sources/league-scheduling/generated/MatchupPair.pb.swift index 740ba2f..5f9f8f1 100644 --- a/Sources/league-scheduling/generated/MatchupPair.pb.swift +++ b/Sources/league-scheduling/generated/MatchupPair.pb.swift @@ -53,10 +53,10 @@ public struct LitLeagues_Leagues_MatchupPair: Sendable { // `Message` and `Message+*Additions` files in the SwiftProtobuf library for // methods supported on all messages. - /// LeagueEntry.IDValue (usually the home team) + /// Entry.IDValue (usually the home team) public var team1: UInt32 = 0 - /// LeagueEntry.IDValue (usually the away team) + /// Entry.IDValue (usually the away team) public var team2: UInt32 = 0 public var unknownFields = SwiftProtobuf.UnknownStorage() diff --git a/Sources/league-scheduling/generated/RequestPayload.pb.swift b/Sources/league-scheduling/generated/RequestPayload.pb.swift index ce4e4a4..ac722d7 100644 --- a/Sources/league-scheduling/generated/RequestPayload.pb.swift +++ b/Sources/league-scheduling/generated/RequestPayload.pb.swift @@ -100,6 +100,16 @@ public struct LitLeagues_Leagues_RequestPayload: Sendable { /// The entries/teams being scheduled, including their settings. public var entries: [LitLeagues_Leagues_Entry] = [] + /// Constraints that influence the schedule generation process. + public var generationConstraints: LitLeagues_Leagues_GenerationConstraints { + get {return _generationConstraints ?? LitLeagues_Leagues_GenerationConstraints()} + set {_generationConstraints = newValue} + } + /// Returns true if `generationConstraints` has been explicitly set. + public var hasGenerationConstraints: Bool {return self._generationConstraints != nil} + /// Clears the value of `generationConstraints`. Subsequent reads from it will return its default value. + public mutating func clearGenerationConstraints() {self._generationConstraints = nil} + public var unknownFields = SwiftProtobuf.UnknownStorage() public init() {} @@ -108,6 +118,7 @@ public struct LitLeagues_Leagues_RequestPayload: Sendable { fileprivate var _settings: LitLeagues_Leagues_GeneralSettings? = nil fileprivate var _individualDaySettings: LitLeagues_Leagues_DaySettingsArray? = nil fileprivate var _divisions: LitLeagues_Leagues_Divisions? = nil + fileprivate var _generationConstraints: LitLeagues_Leagues_GenerationConstraints? = nil } // MARK: - Code below here is support for the SwiftProtobuf runtime. @@ -116,7 +127,7 @@ fileprivate let _protobuf_package = "lit_leagues.leagues" extension LitLeagues_Leagues_RequestPayload: SwiftProtobuf.Message, SwiftProtobuf._MessageImplementationBase, SwiftProtobuf._ProtoNameProviding { public static let protoMessageName: String = _protobuf_package + ".RequestPayload" - public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}starts\0\u{1}gameDays\0\u{1}settings\0\u{1}individualDaySettings\0\u{1}divisions\0\u{1}entries\0") + public static let _protobuf_nameMap = SwiftProtobuf._NameMap(bytecode: "\0\u{1}starts\0\u{1}gameDays\0\u{1}settings\0\u{1}individualDaySettings\0\u{1}divisions\0\u{1}entries\0\u{1}generationConstraints\0") public mutating func decodeMessage(decoder: inout D) throws { while let fieldNumber = try decoder.nextFieldNumber() { @@ -130,6 +141,7 @@ extension LitLeagues_Leagues_RequestPayload: SwiftProtobuf.Message, SwiftProtobu case 4: try { try decoder.decodeSingularMessageField(value: &self._individualDaySettings) }() case 5: try { try decoder.decodeSingularMessageField(value: &self._divisions) }() case 6: try { try decoder.decodeRepeatedMessageField(value: &self.entries) }() + case 7: try { try decoder.decodeSingularMessageField(value: &self._generationConstraints) }() default: break } } @@ -158,6 +170,9 @@ extension LitLeagues_Leagues_RequestPayload: SwiftProtobuf.Message, SwiftProtobu if !self.entries.isEmpty { try visitor.visitRepeatedMessageField(value: self.entries, fieldNumber: 6) } + try { if let v = self._generationConstraints { + try visitor.visitSingularMessageField(value: v, fieldNumber: 7) + } }() try unknownFields.traverse(visitor: &visitor) } @@ -168,7 +183,8 @@ extension LitLeagues_Leagues_RequestPayload: SwiftProtobuf.Message, SwiftProtobu if lhs._individualDaySettings != rhs._individualDaySettings {return false} if lhs._divisions != rhs._divisions {return false} if lhs.entries != rhs.entries {return false} + if lhs._generationConstraints != rhs._generationConstraints {return false} if lhs.unknownFields != rhs.unknownFields {return false} return true } -} +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/AvailableSlot+Codable.swift b/Sources/league-scheduling/generated/codable/AvailableSlot+Codable.swift new file mode 100644 index 0000000..95ecc90 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/AvailableSlot+Codable.swift @@ -0,0 +1,21 @@ + +#if ProtobufCodable +extension AvailableSlot: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + time = try container.decode(TimeIndex.self, forKey: .time) + location = try container.decode(LocationIndex.self, forKey: .location) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(time, forKey: .time) + try container.encode(location, forKey: .location) + } + + enum CodingKeys: CodingKey { + case time + case location + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/BalanceStrictness+Codable.swift b/Sources/league-scheduling/generated/codable/BalanceStrictness+Codable.swift new file mode 100644 index 0000000..7942e91 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/BalanceStrictness+Codable.swift @@ -0,0 +1,5 @@ + +#if ProtobufCodable +extension BalanceStrictness: Codable { +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/Byes+Extensions.swift b/Sources/league-scheduling/generated/codable/Byes+Codable.swift similarity index 79% rename from Sources/league-scheduling/generated/extensions/Byes+Extensions.swift rename to Sources/league-scheduling/generated/codable/Byes+Codable.swift index 274f9f1..904c203 100644 --- a/Sources/league-scheduling/generated/extensions/Byes+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/Byes+Codable.swift @@ -1,13 +1,14 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_Byes: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - byes = try container.decode([LeagueDayIndex].self) + byes = try container.decode([DayIndex].self) } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(byes) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/DaySettings+Extensions.swift b/Sources/league-scheduling/generated/codable/DaySettings+Codable.swift similarity index 68% rename from Sources/league-scheduling/generated/extensions/DaySettings+Extensions.swift rename to Sources/league-scheduling/generated/codable/DaySettings+Codable.swift index 0982a5f..a6b5909 100644 --- a/Sources/league-scheduling/generated/extensions/DaySettings+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/DaySettings+Codable.swift @@ -1,9 +1,9 @@ -// MARK: Codable -extension LeagueDaySettings: Codable { +#if ProtobufCodable +extension DaySettings: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - if let v = try container.decodeIfPresent(LeagueGeneralSettings.self, forKey: .settings) { + if let v = try container.decodeIfPresent(GeneralSettings.self, forKey: .settings) { settings = v } } @@ -15,7 +15,8 @@ extension LeagueDaySettings: Codable { } } - public enum CodingKeys: CodingKey { + enum CodingKeys: CodingKey { case settings } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/DaySettingsArray+Extensions.swift b/Sources/league-scheduling/generated/codable/DaySettingsArray+Codable.swift similarity index 93% rename from Sources/league-scheduling/generated/extensions/DaySettingsArray+Extensions.swift rename to Sources/league-scheduling/generated/codable/DaySettingsArray+Codable.swift index 5940a4f..ebd7e09 100644 --- a/Sources/league-scheduling/generated/extensions/DaySettingsArray+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/DaySettingsArray+Codable.swift @@ -1,5 +1,5 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_DaySettingsArray: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() @@ -10,4 +10,5 @@ extension LitLeagues_Leagues_DaySettingsArray: Codable { var container = encoder.singleValueContainer() try container.encode(days) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/Division+Codable.swift b/Sources/league-scheduling/generated/codable/Division+Codable.swift new file mode 100644 index 0000000..9eab26c --- /dev/null +++ b/Sources/league-scheduling/generated/codable/Division+Codable.swift @@ -0,0 +1,99 @@ + +#if ProtobufCodable +extension Division: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if let v = try container.decodeIfPresent(String.self, forKey: .name) { + self.name = v + } + if let v = try container.decodeIfPresent(UInt32.self, forKey: .dayOfWeek) { + self.dayOfWeek = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_GameDays.self, forKey: .gameDays) { + self.gameDays = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_Byes.self, forKey: .byes) { + self.byes = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_GameGaps.self, forKey: .gameGaps) { + self.gameGaps = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_GameDayTimes.self, forKey: .gameDayTimes) { + self.gameDayTimes = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_GameDayLocations.self, forKey: .gameDayLocations) { + self.gameDayLocations = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_DivisionMatchupDurations.self, forKey: .matchupDurations) { + self.matchupDurations = v + } + if let v = try container.decodeIfPresent(LocationTimeExclusivities.self, forKey: .locationTimeExclusivities) { + self.locationTimeExclusivities = v + } + if let v = try container.decodeIfPresent(LocationTravelDurations.self, forKey: .travelDurations) { + self.travelDurations = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_DivisionOpponents.self, forKey: .opponents) { + self.opponents = v + } + if let v = try container.decodeIfPresent(MaximumSameOpponentMatchupsCap.self, forKey: .maxSameOpponentMatchups) { + self.maxSameOpponentMatchups = v + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if hasName { + try container.encode(name, forKey: .name) + } + if hasDayOfWeek { + try container.encode(dayOfWeek, forKey: .dayOfWeek) + } + if hasGameDays { + try container.encode(gameDays, forKey: .gameDays) + } + if hasByes { + try container.encode(byes, forKey: .byes) + } + if hasGameGaps { + try container.encode(gameGaps, forKey: .gameGaps) + } + if hasGameDayTimes { + try container.encode(gameDayTimes, forKey: .gameDayTimes) + } + if hasGameDayLocations { + try container.encode(gameDayLocations, forKey: .gameDayLocations) + } + if hasMatchupDurations { + try container.encode(matchupDurations, forKey: .matchupDurations) + } + if hasLocationTimeExclusivities { + try container.encode(locationTimeExclusivities, forKey: .locationTimeExclusivities) + } + if hasTravelDurations { + try container.encode(travelDurations, forKey: .travelDurations) + } + if hasOpponents { + try container.encode(opponents, forKey: .opponents) + } + if hasMaxSameOpponentMatchups { + try container.encode(maxSameOpponentMatchups, forKey: .maxSameOpponentMatchups) + } + } + + enum CodingKeys: CodingKey { + case name + case dayOfWeek + case gameDays + case byes + case gameGaps + case gameDayTimes + case gameDayLocations + case matchupDurations + case locationTimeExclusivities + case travelDurations + case opponents + case maxSameOpponentMatchups + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/DivisionMatchupDurations+Extensions.swift b/Sources/league-scheduling/generated/codable/DivisionMatchupDurations+Codable.swift similarity index 93% rename from Sources/league-scheduling/generated/extensions/DivisionMatchupDurations+Extensions.swift rename to Sources/league-scheduling/generated/codable/DivisionMatchupDurations+Codable.swift index 5b921f0..9bc7460 100644 --- a/Sources/league-scheduling/generated/extensions/DivisionMatchupDurations+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/DivisionMatchupDurations+Codable.swift @@ -1,5 +1,5 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_DivisionMatchupDurations: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() @@ -10,4 +10,5 @@ extension LitLeagues_Leagues_DivisionMatchupDurations: Codable { var container = encoder.singleValueContainer() try container.encode(durations) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/DivisionOpponents+Extensions.swift b/Sources/league-scheduling/generated/codable/DivisionOpponents+Codable.swift similarity index 76% rename from Sources/league-scheduling/generated/extensions/DivisionOpponents+Extensions.swift rename to Sources/league-scheduling/generated/codable/DivisionOpponents+Codable.swift index c3db132..49eb6d5 100644 --- a/Sources/league-scheduling/generated/extensions/DivisionOpponents+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/DivisionOpponents+Codable.swift @@ -1,13 +1,14 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_DivisionOpponents: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - divisionOpponentIds = try container.decode([LeagueDivision.IDValue].self) + divisionOpponentIds = try container.decode([Division.IDValue].self) } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(divisionOpponentIds) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/Divisions+Codable.swift b/Sources/league-scheduling/generated/codable/Divisions+Codable.swift new file mode 100644 index 0000000..68e2bae --- /dev/null +++ b/Sources/league-scheduling/generated/codable/Divisions+Codable.swift @@ -0,0 +1,14 @@ + +#if ProtobufCodable +extension LitLeagues_Leagues_Divisions: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + divisions = try container.decode([Division].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(divisions) + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/Entry+Codable.swift b/Sources/league-scheduling/generated/codable/Entry+Codable.swift new file mode 100644 index 0000000..941a327 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/Entry+Codable.swift @@ -0,0 +1,71 @@ + +#if ProtobufCodable +extension Entry: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if let v = try container.decodeIfPresent(String.self, forKey: .name) { + self.name = v + } + if let v = try container.decodeIfPresent(Division.IDValue.self, forKey: .division) { + division = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_GameDays.self, forKey: .gameDays) { + self.gameDays = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_GameDayTimes.self, forKey: .gameDayTimes) { + self.gameDayTimes = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_GameDayLocations.self, forKey: .gameDayLocations) { + self.gameDayLocations = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_EntryHomeLocations.self, forKey: .homeLocations) { + self.homeLocations = v + } + if let v = try container.decodeIfPresent(LitLeagues_Leagues_Byes.self, forKey: .byes) { + self.byes = v + } + if let v = try container.decodeIfPresent([EntryMatchupsPerGameDay].self, forKey: .gameDayMatchups) { + self.matchupsPerGameDay = .init(gameDayMatchups: v) + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if hasName { + try container.encode(name, forKey: .name) + } + if hasDivision { + try container.encode(division, forKey: .division) + } + if hasGameDays { + try container.encode(gameDays, forKey: .gameDays) + } + if hasGameDayTimes { + try container.encode(gameDayTimes, forKey: .gameDayTimes) + } + if hasGameDayLocations { + try container.encode(gameDayLocations, forKey: .gameDayLocations) + } + if hasHomeLocations { + try container.encode(homeLocations.homeLocations, forKey: .homeLocations) + } + if hasByes { + try container.encode(byes.byes, forKey: .byes) + } + if hasMatchupsPerGameDay { + try container.encode(matchupsPerGameDay.gameDayMatchups, forKey: .gameDayMatchups) + } + } + + enum CodingKeys: CodingKey { + case name + case division + case gameDays + case gameDayTimes + case gameDayLocations + case homeLocations + case byes + case gameDayMatchups + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueEntryHomeLocations+Extensions.swift b/Sources/league-scheduling/generated/codable/EntryHomeLocations+Codable.swift similarity index 78% rename from Sources/league-scheduling/generated/extensions/LeagueEntryHomeLocations+Extensions.swift rename to Sources/league-scheduling/generated/codable/EntryHomeLocations+Codable.swift index 7506cc8..5871236 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueEntryHomeLocations+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/EntryHomeLocations+Codable.swift @@ -1,13 +1,14 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_EntryHomeLocations: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - homeLocations = try container.decode([LeagueLocationIndex].self) + homeLocations = try container.decode([LocationIndex].self) } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(homeLocations) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueEntryMatchupsPerGameDay+Extensions.swift b/Sources/league-scheduling/generated/codable/EntryMatchupsPerGameDay+Codable.swift similarity index 60% rename from Sources/league-scheduling/generated/extensions/LeagueEntryMatchupsPerGameDay+Extensions.swift rename to Sources/league-scheduling/generated/codable/EntryMatchupsPerGameDay+Codable.swift index d4fe822..cc3791b 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueEntryMatchupsPerGameDay+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/EntryMatchupsPerGameDay+Codable.swift @@ -1,9 +1,9 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_EntryMatchupsPerGameDay: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) - gameDayMatchups = try container.decode([LeagueDayIndex].self, forKey: .gameDayMatchups) + gameDayMatchups = try container.decode([DayIndex].self, forKey: .gameDayMatchups) } public func encode(to encoder: any Encoder) throws { @@ -15,10 +15,4 @@ extension LitLeagues_Leagues_EntryMatchupsPerGameDay: Codable { case gameDayMatchups } } - -// MARK: Initializer -extension LitLeagues_Leagues_EntryMatchupsPerGameDay { - init(gameDayMatchups: [LeagueEntryMatchupsPerGameDay]) { - self.gameDayMatchups = gameDayMatchups - } -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LocationTimeExclusivities+Extensions.swift b/Sources/league-scheduling/generated/codable/GameDayLocations+Codable.swift similarity index 64% rename from Sources/league-scheduling/generated/extensions/LocationTimeExclusivities+Extensions.swift rename to Sources/league-scheduling/generated/codable/GameDayLocations+Codable.swift index c91ef01..98e2248 100644 --- a/Sources/league-scheduling/generated/extensions/LocationTimeExclusivities+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/GameDayLocations+Codable.swift @@ -1,14 +1,14 @@ -// MARK: Codable -extension LeagueLocationTimeExclusivities: Codable { - +#if ProtobufCodable +extension LitLeagues_Leagues_GameDayLocations: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - locations = try container.decode([LeagueLocationTimeExclusivity].self) + locations = try container.decode([GameLocations].self) } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(locations) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueGameDayTimes+Extensions.swift b/Sources/league-scheduling/generated/codable/GameDayTimes+Codable.swift similarity index 58% rename from Sources/league-scheduling/generated/extensions/LeagueGameDayTimes+Extensions.swift rename to Sources/league-scheduling/generated/codable/GameDayTimes+Codable.swift index 533dc26..b553223 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueGameDayTimes+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/GameDayTimes+Codable.swift @@ -1,9 +1,9 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_GameDayTimes: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - times = try container.decode([LeagueGameTimes].self) + times = try container.decode([GameTimes].self) } public func encode(to encoder: any Encoder) throws { @@ -11,12 +11,4 @@ extension LitLeagues_Leagues_GameDayTimes: Codable { try container.encode(times) } } - -// MARK: Init -extension LitLeagues_Leagues_GameDayTimes { - public init( - times: [LeagueGameTimes] - ) { - self.times = times - } -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/GameDays+Codable.swift b/Sources/league-scheduling/generated/codable/GameDays+Codable.swift new file mode 100644 index 0000000..a70d2c8 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/GameDays+Codable.swift @@ -0,0 +1,14 @@ + +#if ProtobufCodable +extension LitLeagues_Leagues_GameDays: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + gameDayIndexes = try container.decode([DayIndex].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(gameDayIndexes) + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/GameGaps+Extensions.swift b/Sources/league-scheduling/generated/codable/GameGaps+Codable.swift similarity index 93% rename from Sources/league-scheduling/generated/extensions/GameGaps+Extensions.swift rename to Sources/league-scheduling/generated/codable/GameGaps+Codable.swift index 53e27e9..685aab1 100644 --- a/Sources/league-scheduling/generated/extensions/GameGaps+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/GameGaps+Codable.swift @@ -1,5 +1,5 @@ -// MARK: Codable +#if ProtobufCodable extension LitLeagues_Leagues_GameGaps: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() @@ -10,4 +10,5 @@ extension LitLeagues_Leagues_GameGaps: Codable { var container = encoder.singleValueContainer() try container.encode(gameGaps) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LocationTravelDurations+Extensions.swift b/Sources/league-scheduling/generated/codable/GameLocations+Codable.swift similarity index 64% rename from Sources/league-scheduling/generated/extensions/LocationTravelDurations+Extensions.swift rename to Sources/league-scheduling/generated/codable/GameLocations+Codable.swift index b2ff3e3..d85fda8 100644 --- a/Sources/league-scheduling/generated/extensions/LocationTravelDurations+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/GameLocations+Codable.swift @@ -1,14 +1,14 @@ -// MARK: Codable -extension LeagueLocationTravelDurations: Codable { - +#if ProtobufCodable +extension GameLocations: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - locations = try container.decode([LeagueLocationTravelDurationFrom].self) + locations = try container.decode([LocationIndex].self) } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(locations) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LocationTimeExclusivity+Extensions.swift b/Sources/league-scheduling/generated/codable/GameTimes+Codable.swift similarity index 67% rename from Sources/league-scheduling/generated/extensions/LocationTimeExclusivity+Extensions.swift rename to Sources/league-scheduling/generated/codable/GameTimes+Codable.swift index cb08f75..8db6e7c 100644 --- a/Sources/league-scheduling/generated/extensions/LocationTimeExclusivity+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/GameTimes+Codable.swift @@ -1,14 +1,14 @@ -// MARK: Codable -extension LeagueLocationTimeExclusivity: Codable { - +#if ProtobufCodable +extension GameTimes: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - times = try container.decode([LeagueTimeIndex].self) + times = try container.decode([TimeIndex].self) } public func encode(to encoder: any Encoder) throws { var container = encoder.singleValueContainer() try container.encode(times) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueGeneralSettings+Extensions.swift b/Sources/league-scheduling/generated/codable/GeneralSettings+Codable.swift similarity index 50% rename from Sources/league-scheduling/generated/extensions/LeagueGeneralSettings+Extensions.swift rename to Sources/league-scheduling/generated/codable/GeneralSettings+Codable.swift index d78f17c..9a89db6 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueGeneralSettings+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/GeneralSettings+Codable.swift @@ -1,48 +1,48 @@ -import StaticDateTimes +#if ProtobufCodable -// MARK: Codable -extension LeagueGeneralSettings: Codable { +import StaticDateTimes +extension GeneralSettings: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let v = try container.decodeIfPresent(String.self, forKey: .gameGap) { gameGap = v } - if let v = try container.decodeIfPresent(LeagueTimeIndex.self, forKey: .timeSlots) { + if let v = try container.decodeIfPresent(TimeIndex.self, forKey: .timeSlots) { timeSlots = v } if let v = try container.decodeIfPresent(LitLeagues_Leagues_StaticTimes.self, forKey: .startingTimes) { startingTimes = v } - if let v = try container.decodeIfPresent(LeagueEntriesPerMatchup.self, forKey: .entriesPerLocation) { + if let v = try container.decodeIfPresent(EntriesPerMatchup.self, forKey: .entriesPerLocation) { entriesPerLocation = v } - if let v = try container.decodeIfPresent(LeagueLocationIndex.self, forKey: .locations) { + if let v = try container.decodeIfPresent(LocationIndex.self, forKey: .locations) { locations = v } - if let v = try container.decodeIfPresent(LeagueEntryMatchupsPerGameDay.self, forKey: .entryMatchupsPerGameDay) { + if let v = try container.decodeIfPresent(EntryMatchupsPerGameDay.self, forKey: .entryMatchupsPerGameDay) { entryMatchupsPerGameDay = v } if let v = try container.decodeIfPresent(LitLeagues_Leagues_UInt32Array.self, forKey: .maximumPlayableMatchups) { maximumPlayableMatchups = v } - if let v = try container.decodeIfPresent(LeagueMatchupDuration.self, forKey: .matchupDuration) { + if let v = try container.decodeIfPresent(MatchupDuration.self, forKey: .matchupDuration) { matchupDuration = v } - if let v = try container.decodeIfPresent(LeagueLocationTimeExclusivities.self, forKey: .locationTimeExclusivities) { + if let v = try container.decodeIfPresent(LocationTimeExclusivities.self, forKey: .locationTimeExclusivities) { locationTimeExclusivities = v } - if let v = try container.decodeIfPresent(LeagueLocationTravelDurations.self, forKey: .locationTravelDurations) { + if let v = try container.decodeIfPresent(LocationTravelDurations.self, forKey: .locationTravelDurations) { locationTravelDurations = v } - if let v = try container.decodeIfPresent(LeagueBalanceStrictness.self, forKey: .balanceTimeStrictness) { + if let v = try container.decodeIfPresent(BalanceStrictness.self, forKey: .balanceTimeStrictness) { balanceTimeStrictness = v } if let v = try container.decodeIfPresent(LitLeagues_Leagues_UInt32Array.self, forKey: .balancedTimes) { balancedTimes = v } - if let v = try container.decodeIfPresent(LeagueBalanceStrictness.self, forKey: .balanceLocationStrictness) { + if let v = try container.decodeIfPresent(BalanceStrictness.self, forKey: .balanceLocationStrictness) { balanceLocationStrictness = v } if let v = try container.decodeIfPresent(LitLeagues_Leagues_UInt32Array.self, forKey: .balancedLocations) { @@ -76,7 +76,9 @@ extension LeagueGeneralSettings: Codable { if hasEntryMatchupsPerGameDay { try container.encode(entryMatchupsPerGameDay, forKey: .entryMatchupsPerGameDay) } - try container.encode(maximumPlayableMatchups, forKey: .maximumPlayableMatchups) + if hasMaximumPlayableMatchups { + try container.encode(maximumPlayableMatchups, forKey: .maximumPlayableMatchups) + } if hasMatchupDuration { try container.encode(matchupDuration, forKey: .matchupDuration) } @@ -106,7 +108,7 @@ extension LeagueGeneralSettings: Codable { } } - public enum CodingKeys: CodingKey { + enum CodingKeys: CodingKey { case gameGap case entriesPerLocation case timeSlots @@ -125,78 +127,4 @@ extension LeagueGeneralSettings: Codable { case flags } } - -// MARK: Initialization -extension LeagueGeneralSettings { - package init( - gameGap: String, - timeSlots: LeagueTimeIndex, - startingTimes: [StaticTime], - entriesPerLocation: LeagueEntriesPerMatchup, - locations: LeagueLocationIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, - maximumPlayableMatchups: [UInt32], - matchupDuration: Double? = nil, - locationTimeExclusivities: LeagueLocationTimeExclusivities? = nil, - locationTravelDurations: LeagueLocationTravelDurations? = nil, - balanceTimeStrictness: LeagueBalanceStrictness, - balancedTimes: [LeagueTimeIndex], - balanceLocationStrictness: LeagueBalanceStrictness, - balancedLocations: [LeagueLocationIndex], - flags: UInt32 - ) { - self.gameGap = gameGap - self.timeSlots = timeSlots - self.startingTimes = .init(times: startingTimes) - self.entriesPerLocation = entriesPerLocation - self.locations = locations - self.entryMatchupsPerGameDay = entryMatchupsPerGameDay - self.maximumPlayableMatchups = .init(array: maximumPlayableMatchups) - if let matchupDuration { - self.matchupDuration = matchupDuration - } - if let locationTimeExclusivities { - self.locationTimeExclusivities = locationTimeExclusivities - } - if let locationTravelDurations { - self.locationTravelDurations = locationTravelDurations - } - self.balanceTimeStrictness = balanceTimeStrictness - self.balancedTimes = .init(array: balancedTimes) - self.balanceLocationStrictness = balanceLocationStrictness - self.balancedLocations = .init(array: balancedLocations) - self.flags = flags - } -} - -// MARK: General -extension LeagueGeneralSettings { - public func isFlag(_ flag: LeagueSettingFlags) -> Bool { - flags & UInt32(1 << flag.rawValue) != 0 - } - - /// If we should try arranging matchups so they fit in less time slots than provided. - public var optimizeTimes: Bool { - isFlag(.optimizeTimes) - } - - /// If we should try arranging matchups so they fill earlier time slots first. - public var prioritizeEarlierTimes: Bool { - isFlag(.prioritizeEarlierTimes) - } - - /// If we should try keeping matchups that play at "home" to play at "home" for later matchups (and the same for "away" matchups). - public var prioritizeHomeAway: Bool { - isFlag(.prioritizeHomeAway) - } - - /// If we should try balancing the number each entry plays each other at "home" and "away". - public var balanceHomeAway: Bool { - isFlag(.balanceHomeAway) - } - - /// If we should try keeping teams on the same location if they play back-to-back matchups. - public var sameLocationIfB2B: Bool { - isFlag(.sameLocationIfBackToBack) - } -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/GenerationConstraints+Codable.swift b/Sources/league-scheduling/generated/codable/GenerationConstraints+Codable.swift new file mode 100644 index 0000000..7372fde --- /dev/null +++ b/Sources/league-scheduling/generated/codable/GenerationConstraints+Codable.swift @@ -0,0 +1,43 @@ + +#if ProtobufCodable +extension GenerationConstraints: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if let v = try container.decodeIfPresent(UInt32.self, forKey: .timeoutDelay) { + timeoutDelay = v + } + if let v = try container.decodeIfPresent(UInt32.self, forKey: .regenerationAttemptsForFirstDay) { + regenerationAttemptsForFirstDay = v + } + if let v = try container.decodeIfPresent(UInt32.self, forKey: .regenerationAttemptsForConsecutiveDay) { + regenerationAttemptsForConsecutiveDay = v + } + if let v = try container.decodeIfPresent(UInt32.self, forKey: .regenerationAttemptsThreshold) { + regenerationAttemptsThreshold = v + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if hasTimeoutDelay { + try container.encode(timeoutDelay, forKey: .timeoutDelay) + } + if hasRegenerationAttemptsForFirstDay { + try container.encode(regenerationAttemptsForFirstDay, forKey: .regenerationAttemptsForFirstDay) + } + if hasRegenerationAttemptsForConsecutiveDay { + try container.encode(regenerationAttemptsForConsecutiveDay, forKey: .regenerationAttemptsForConsecutiveDay) + } + if hasRegenerationAttemptsThreshold { + try container.encode(regenerationAttemptsThreshold, forKey: .regenerationAttemptsThreshold) + } + } + + enum CodingKeys: CodingKey { + case timeoutDelay + case regenerationAttemptsForFirstDay + case regenerationAttemptsForConsecutiveDay + case regenerationAttemptsThreshold + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/LocationTimeExclusivities+Codable.swift b/Sources/league-scheduling/generated/codable/LocationTimeExclusivities+Codable.swift new file mode 100644 index 0000000..b97feaf --- /dev/null +++ b/Sources/league-scheduling/generated/codable/LocationTimeExclusivities+Codable.swift @@ -0,0 +1,14 @@ + +#if ProtobufCodable +extension LocationTimeExclusivities: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + locations = try container.decode([LocationTimeExclusivity].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(locations) + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/LocationTimeExclusivity+Codable.swift b/Sources/league-scheduling/generated/codable/LocationTimeExclusivity+Codable.swift new file mode 100644 index 0000000..e55a77c --- /dev/null +++ b/Sources/league-scheduling/generated/codable/LocationTimeExclusivity+Codable.swift @@ -0,0 +1,14 @@ + +#if ProtobufCodable +extension LocationTimeExclusivity: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + times = try container.decode([TimeIndex].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(times) + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LocationTravelDurationFrom+Extensions.swift b/Sources/league-scheduling/generated/codable/LocationTravelDurationFrom+Codable.swift similarity index 82% rename from Sources/league-scheduling/generated/extensions/LocationTravelDurationFrom+Extensions.swift rename to Sources/league-scheduling/generated/codable/LocationTravelDurationFrom+Codable.swift index e3cf6a4..58ef22f 100644 --- a/Sources/league-scheduling/generated/extensions/LocationTravelDurationFrom+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/LocationTravelDurationFrom+Codable.swift @@ -1,7 +1,6 @@ -// MARK: Codable -extension LeagueLocationTravelDurationFrom: Codable { - +#if ProtobufCodable +extension LocationTravelDurationFrom: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() travelDurationTo = try container.decode([Double].self) @@ -11,4 +10,5 @@ extension LeagueLocationTravelDurationFrom: Codable { var container = encoder.singleValueContainer() try container.encode(travelDurationTo) } -} \ No newline at end of file +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/LocationTravelDurations+Codable.swift b/Sources/league-scheduling/generated/codable/LocationTravelDurations+Codable.swift new file mode 100644 index 0000000..f45ecd1 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/LocationTravelDurations+Codable.swift @@ -0,0 +1,14 @@ + +#if ProtobufCodable +extension LocationTravelDurations: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + locations = try container.decode([LocationTravelDurationFrom].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(locations) + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/Matchup+Codable.swift b/Sources/league-scheduling/generated/codable/Matchup+Codable.swift new file mode 100644 index 0000000..a1f64e5 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/Matchup+Codable.swift @@ -0,0 +1,27 @@ + +#if ProtobufCodable +extension Matchup: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + time = try container.decode(TimeIndex.self, forKey: .time) + location = try container.decode(LocationIndex.self, forKey: .location) + home = try container.decode(Entry.IDValue.self, forKey: .home) + away = try container.decode(Entry.IDValue.self, forKey: .away) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(time, forKey: .time) + try container.encode(location, forKey: .location) + try container.encode(home, forKey: .home) + try container.encode(away, forKey: .away) + } + + enum CodingKeys: CodingKey { + case time + case location + case home + case away + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/MatchupPair+Codable.swift b/Sources/league-scheduling/generated/codable/MatchupPair+Codable.swift new file mode 100644 index 0000000..8bba141 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/MatchupPair+Codable.swift @@ -0,0 +1,21 @@ + +#if ProtobufCodable +extension MatchupPair: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + team1 = try container.decode(Entry.IDValue.self, forKey: .team1) + team2 = try container.decode(Entry.IDValue.self, forKey: .team2) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(team1, forKey: .team1) + try container.encode(team2, forKey: .team2) + } + + enum CodingKeys: CodingKey { + case team1 + case team2 + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/RedistributionSettings+Codable.swift b/Sources/league-scheduling/generated/codable/RedistributionSettings+Codable.swift new file mode 100644 index 0000000..460518f --- /dev/null +++ b/Sources/league-scheduling/generated/codable/RedistributionSettings+Codable.swift @@ -0,0 +1,29 @@ + +#if ProtobufCodable +extension LitLeagues_Leagues_RedistributionSettings: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + if let v = try container.decodeIfPresent(UInt32.self, forKey: .minMatchupsRequired) { + minMatchupsRequired = v + } + if let v = try container.decodeIfPresent(UInt32.self, forKey: .maxMovableMatchups) { + maxMovableMatchups = v + } + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + if hasMinMatchupsRequired { + try container.encode(minMatchupsRequired, forKey: .minMatchupsRequired) + } + if hasMaxMovableMatchups { + try container.encode(maxMovableMatchups, forKey: .maxMovableMatchups) + } + } + + enum CodingKeys: CodingKey { + case minMatchupsRequired + case maxMovableMatchups + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Extensions.swift b/Sources/league-scheduling/generated/codable/RequestPayload+Codable.swift similarity index 58% rename from Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Extensions.swift rename to Sources/league-scheduling/generated/codable/RequestPayload+Codable.swift index 1a81771..dd302c2 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/RequestPayload+Codable.swift @@ -1,20 +1,20 @@ -// MARK: Codable -extension LeagueRequestPayload: Codable { +#if ProtobufCodable +extension RequestPayload: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) if let v = try container.decodeIfPresent(String.self, forKey: .starts) { starts = v } - gameDays = try container.decode(LeagueDayIndex.self, forKey: .gameDays) - settings = try container.decode(LeagueGeneralSettings.self, forKey: .settings) + gameDays = try container.decode(DayIndex.self, forKey: .gameDays) + settings = try container.decode(GeneralSettings.self, forKey: .settings) if let v = try container.decodeIfPresent(LitLeagues_Leagues_DaySettingsArray.self, forKey: .individualDaySettings) { individualDaySettings = v } if let v = try container.decodeIfPresent(LitLeagues_Leagues_Divisions.self, forKey: .divisions) { divisions = v } - entries = try container.decode([LeagueEntry].self, forKey: .teams) + entries = try container.decode([Entry].self, forKey: .teams) } public func encode(to encoder: any Encoder) throws { @@ -42,31 +42,4 @@ extension LeagueRequestPayload: Codable { case teams } } - -// MARK: Init -extension LeagueRequestPayload { - public init( - starts: String? = nil, - gameDays: LeagueDayIndex, - - settings: LeagueGeneralSettings, - individualDaySettings: LitLeagues_Leagues_DaySettingsArray?, - - divisions: [LeagueDivision], - teams: [LeagueEntry] - ) { - if let starts { - self.starts = starts - } - - self.gameDays = gameDays - - self.settings = settings - if let individualDaySettings { - self.individualDaySettings = individualDaySettings - } - - self.divisions = .init(divisions: divisions) - self.entries = teams - } -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueGameTimes+Extensions.swift b/Sources/league-scheduling/generated/codable/StaticTimes+Codable.swift similarity index 55% rename from Sources/league-scheduling/generated/extensions/LeagueGameTimes+Extensions.swift rename to Sources/league-scheduling/generated/codable/StaticTimes+Codable.swift index a72efef..b7b998d 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueGameTimes+Extensions.swift +++ b/Sources/league-scheduling/generated/codable/StaticTimes+Codable.swift @@ -1,10 +1,12 @@ -// MARK: Codable -extension LeagueGameTimes: Codable { +#if ProtobufCodable +import StaticDateTimes + +extension LitLeagues_Leagues_StaticTimes: Codable { public init(from decoder: any Decoder) throws { let container = try decoder.singleValueContainer() - times = try container.decode([LeagueTimeIndex].self) + times = try container.decode([StaticTime].self) } public func encode(to encoder: any Encoder) throws { @@ -12,10 +14,4 @@ extension LeagueGameTimes: Codable { try container.encode(times) } } - -// MARK: General -extension LeagueGameTimes { - var set: Set { - Set(times) - } -} \ No newline at end of file +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/codable/UInt32Array+Codable.swift b/Sources/league-scheduling/generated/codable/UInt32Array+Codable.swift new file mode 100644 index 0000000..a54a584 --- /dev/null +++ b/Sources/league-scheduling/generated/codable/UInt32Array+Codable.swift @@ -0,0 +1,14 @@ + +#if ProtobufCodable +extension LitLeagues_Leagues_UInt32Array: Codable { + public init(from decoder: any Decoder) throws { + let container = try decoder.singleValueContainer() + array = try container.decode([UInt32].self) + } + + public func encode(to encoder: any Encoder) throws { + var container = encoder.singleValueContainer() + try container.encode(array) + } +} +#endif \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/AvailableSlot+Extensions.swift b/Sources/league-scheduling/generated/extensions/AvailableSlot+Extensions.swift index 45e163f..96cdda6 100644 --- a/Sources/league-scheduling/generated/extensions/AvailableSlot+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/AvailableSlot+Extensions.swift @@ -1,35 +1,13 @@ - -// MARK: Codable -extension LeagueAvailableSlot: Codable { - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - time = try container.decode(LeagueTimeIndex.self, forKey: .time) - location = try container.decode(LeagueLocationIndex.self, forKey: .location) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(time, forKey: .time) - try container.encode(location, forKey: .location) - } - - public enum CodingKeys: CodingKey { - case time - case location - } -} - // MARK: CustomStringConvertible -extension LeagueAvailableSlot: CustomStringConvertible { +extension AvailableSlot: CustomStringConvertible { public var description: String { "T\(time)L\(location)" } } // MARK: Hashable -extension LeagueAvailableSlot: Hashable { +extension AvailableSlot: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(time) hasher.combine(location) @@ -37,8 +15,8 @@ extension LeagueAvailableSlot: Hashable { } // MARK: General -extension LeagueAvailableSlot { - public init(time: LeagueTimeIndex, location: LeagueLocationIndex) { +extension AvailableSlot { + init(time: TimeIndex, location: LocationIndex) { self.time = time self.location = location } diff --git a/Sources/league-scheduling/generated/extensions/BalanceStrictness+Extensions.swift b/Sources/league-scheduling/generated/extensions/BalanceStrictness+Extensions.swift index ee04535..903329b 100644 --- a/Sources/league-scheduling/generated/extensions/BalanceStrictness+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/BalanceStrictness+Extensions.swift @@ -1,11 +1,7 @@ -// MARK: Codable -extension LeagueBalanceStrictness: Codable { -} - // MARK: General -extension LeagueBalanceStrictness { - public init?(rawValue: String) { +extension BalanceStrictness { + init?(rawValue: String) { switch rawValue { case "lenient", "LENIENT": self = .lenient case "relaxed", "RELAXED": self = .relaxed diff --git a/Sources/league-scheduling/generated/extensions/Division+Extensions.swift b/Sources/league-scheduling/generated/extensions/Division+Extensions.swift index 527a961..c9c25fb 100644 --- a/Sources/league-scheduling/generated/extensions/Division+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/Division+Extensions.swift @@ -1,103 +1,4 @@ -// MARK: Codable -extension LeagueDivision: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - if let name = try container.decodeIfPresent(String.self, forKey: .name) { - self.name = name - } - if let dayOfWeek = try container.decodeIfPresent(UInt32.self, forKey: .dayOfWeek) { - self.dayOfWeek = dayOfWeek - } - if let gameDays = try container.decodeIfPresent(LitLeagues_Leagues_GameDays.self, forKey: .gameDays) { - self.gameDays = gameDays - } - if let byes = try container.decodeIfPresent(LitLeagues_Leagues_Byes.self, forKey: .byes) { - self.byes = byes - } - if let gameGaps = try container.decodeIfPresent(LitLeagues_Leagues_GameGaps.self, forKey: .gameGaps) { - self.gameGaps = gameGaps - } - if let gameDayTimes = try container.decodeIfPresent(LitLeagues_Leagues_GameDayTimes.self, forKey: .gameDayTimes) { - self.gameDayTimes = gameDayTimes - } - if let gameDayLocations = try container.decodeIfPresent(LitLeagues_Leagues_GameDayLocations.self, forKey: .gameDayLocations) { - self.gameDayLocations = gameDayLocations - } - if let matchupDurations = try container.decodeIfPresent(LitLeagues_Leagues_DivisionMatchupDurations.self, forKey: .matchupDurations) { - self.matchupDurations = matchupDurations - } - if let locationTimeExclusivities = try container.decodeIfPresent(LeagueLocationTimeExclusivities.self, forKey: .locationTimeExclusivities) { - self.locationTimeExclusivities = locationTimeExclusivities - } - if let travelDurations = try container.decodeIfPresent(LeagueLocationTravelDurations.self, forKey: .travelDurations) { - self.travelDurations = travelDurations - } - if let opponents = try container.decodeIfPresent(LitLeagues_Leagues_DivisionOpponents.self, forKey: .opponents) { - self.opponents = opponents - } - if let maxSameOpponentMatchups = try container.decodeIfPresent(LeagueMaximumSameOpponentMatchupsCap.self, forKey: .maxSameOpponentMatchups) { - self.maxSameOpponentMatchups = maxSameOpponentMatchups - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - if hasName { - try container.encode(name, forKey: .name) - } - if hasDayOfWeek { - try container.encode(dayOfWeek, forKey: .dayOfWeek) - } - if hasGameDays { - try container.encode(gameDays, forKey: .gameDays) - } - if hasByes { - try container.encode(byes, forKey: .byes) - } - if hasGameGaps { - try container.encode(gameGaps, forKey: .gameGaps) - } - if hasGameDayTimes { - try container.encode(gameDayTimes, forKey: .gameDayTimes) - } - if hasGameDayLocations { - try container.encode(gameDayLocations, forKey: .gameDayLocations) - } - if hasMatchupDurations { - try container.encode(matchupDurations, forKey: .matchupDurations) - } - if hasLocationTimeExclusivities { - try container.encode(locationTimeExclusivities, forKey: .locationTimeExclusivities) - } - if hasTravelDurations { - try container.encode(travelDurations, forKey: .travelDurations) - } - if hasOpponents { - try container.encode(opponents, forKey: .opponents) - } - if hasMaxSameOpponentMatchups { - try container.encode(maxSameOpponentMatchups, forKey: .maxSameOpponentMatchups) - } - } - - public enum CodingKeys: CodingKey { - case name - case dayOfWeek - case gameDays - case byes - case gameGaps - case gameDayTimes - case gameDayLocations - case matchupDurations - case locationTimeExclusivities - case travelDurations - case opponents - case maxSameOpponentMatchups - } -} - -// MARK: General -extension LeagueDivision { - public typealias IDValue = UInt32 +extension Division { + typealias IDValue = UInt32 } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/Divisions+Extensions.swift b/Sources/league-scheduling/generated/extensions/Divisions+Extensions.swift index f3c9334..3c4c3a6 100644 --- a/Sources/league-scheduling/generated/extensions/Divisions+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/Divisions+Extensions.swift @@ -1,21 +1,7 @@ -// MARK: Codable -extension LitLeagues_Leagues_Divisions: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - divisions = try container.decode([LeagueDivision].self) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(divisions) - } -} - -// MARK: Init extension LitLeagues_Leagues_Divisions { - public init( - divisions: [LeagueDivision] + init( + divisions: [Division] ) { self.divisions = divisions } diff --git a/Sources/league-scheduling/generated/extensions/Entry+Extensions.swift b/Sources/league-scheduling/generated/extensions/Entry+Extensions.swift index 44608d3..7553848 100644 --- a/Sources/league-scheduling/generated/extensions/Entry+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/Entry+Extensions.swift @@ -1,84 +1,15 @@ +extension Entry { + typealias IDValue = UInt32 -// MARK: Codable -extension LeagueEntry: Codable { - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - if let name = try container.decodeIfPresent(String.self, forKey: .name) { - self.name = name - } - division = try container.decode(LeagueDivision.IDValue.self, forKey: .division) - if let gameDays = try container.decodeIfPresent(LitLeagues_Leagues_GameDays.self, forKey: .gameDays) { - self.gameDays = gameDays - } - if let gameDayTimes = try container.decodeIfPresent(LitLeagues_Leagues_GameDayTimes.self, forKey: .gameDayTimes) { - self.gameDayTimes = gameDayTimes - } - if let gameDayLocations = try container.decodeIfPresent(LitLeagues_Leagues_GameDayLocations.self, forKey: .gameDayLocations) { - self.gameDayLocations = gameDayLocations - } - if let homeLocations = try container.decodeIfPresent(LitLeagues_Leagues_EntryHomeLocations.self, forKey: .homeLocations) { - self.homeLocations = homeLocations - } - if let byes = try container.decodeIfPresent(LitLeagues_Leagues_Byes.self, forKey: .byes) { - self.byes = byes - } - if let matchupsPerGameDay = try container.decodeIfPresent([LeagueEntryMatchupsPerGameDay].self, forKey: .gameDayMatchups) { - self.matchupsPerGameDay = .init(gameDayMatchups: matchupsPerGameDay) - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - if hasName { - try container.encode(name, forKey: .name) - } - try container.encode(division, forKey: .division) - if hasGameDays { - try container.encode(gameDays, forKey: .gameDays) - } - if hasGameDayTimes { - try container.encode(gameDayTimes, forKey: .gameDayTimes) - } - if hasGameDayLocations { - try container.encode(gameDayLocations, forKey: .gameDayLocations) - } - if hasHomeLocations { - try container.encode(homeLocations.homeLocations, forKey: .homeLocations) - } - if hasByes { - try container.encode(byes.byes, forKey: .byes) - } - if hasMatchupsPerGameDay { - try container.encode(matchupsPerGameDay.gameDayMatchups, forKey: .gameDayMatchups) - } - } - - public enum CodingKeys: CodingKey { - case name - case division - case gameDays - case gameDayTimes - case gameDayLocations - case homeLocations - case byes - case gameDayMatchups - } -} - -// MARK: General -extension LeagueEntry { - public typealias IDValue = UInt32 - - public init( - division: LeagueDivision.IDValue, + init( + division: Division.IDValue, gameDays: LitLeagues_Leagues_GameDays?, gameDayTimes: LitLeagues_Leagues_GameDayTimes?, gameDayLocations: LitLeagues_Leagues_GameDayLocations?, homeLocations: LitLeagues_Leagues_EntryHomeLocations?, byes: LitLeagues_Leagues_Byes?, - matchupsPerGameDay: [LeagueEntryMatchupsPerGameDay]? = nil + matchupsPerGameDay: [EntryMatchupsPerGameDay]? = nil ) { self.division = division if let gameDays { diff --git a/Sources/league-scheduling/generated/extensions/EntryMatchupsPerGameDay+Extensions.swift b/Sources/league-scheduling/generated/extensions/EntryMatchupsPerGameDay+Extensions.swift new file mode 100644 index 0000000..73d41dd --- /dev/null +++ b/Sources/league-scheduling/generated/extensions/EntryMatchupsPerGameDay+Extensions.swift @@ -0,0 +1,6 @@ + +extension LitLeagues_Leagues_EntryMatchupsPerGameDay { + init(gameDayMatchups: [EntryMatchupsPerGameDay]) { + self.gameDayMatchups = gameDayMatchups + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/GameDayLocations+Extensions.swift b/Sources/league-scheduling/generated/extensions/GameDayLocations+Extensions.swift new file mode 100644 index 0000000..d8d6b1e --- /dev/null +++ b/Sources/league-scheduling/generated/extensions/GameDayLocations+Extensions.swift @@ -0,0 +1,8 @@ + +extension LitLeagues_Leagues_GameDayLocations { + init( + locations: [GameLocations] + ) { + self.locations = locations + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/GameDayTimes+Extensions.swift b/Sources/league-scheduling/generated/extensions/GameDayTimes+Extensions.swift new file mode 100644 index 0000000..7023623 --- /dev/null +++ b/Sources/league-scheduling/generated/extensions/GameDayTimes+Extensions.swift @@ -0,0 +1,8 @@ + +extension LitLeagues_Leagues_GameDayTimes { + init( + times: [GameTimes] + ) { + self.times = times + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/GameDays+Extensions.swift b/Sources/league-scheduling/generated/extensions/GameDays+Extensions.swift index a08718f..f50a8c6 100644 --- a/Sources/league-scheduling/generated/extensions/GameDays+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/GameDays+Extensions.swift @@ -1,21 +1,7 @@ -// MARK: Codable -extension LitLeagues_Leagues_GameDays: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - gameDayIndexes = try container.decode([LeagueDayIndex].self) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(gameDayIndexes) - } -} - -// MARK: Init extension LitLeagues_Leagues_GameDays { - public init( - gameDayIndexes: [LeagueDayIndex] + init( + gameDayIndexes: [DayIndex] ) { self.gameDayIndexes = gameDayIndexes } diff --git a/Sources/league-scheduling/generated/extensions/GeneralSettings+Extensions.swift b/Sources/league-scheduling/generated/extensions/GeneralSettings+Extensions.swift new file mode 100644 index 0000000..096aa26 --- /dev/null +++ b/Sources/league-scheduling/generated/extensions/GeneralSettings+Extensions.swift @@ -0,0 +1,77 @@ + +import StaticDateTimes + +// MARK: Initialization +extension GeneralSettings { + init( + gameGap: String, + timeSlots: TimeIndex, + startingTimes: [StaticTime], + entriesPerLocation: EntriesPerMatchup, + locations: LocationIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + maximumPlayableMatchups: [UInt32], + matchupDuration: Double? = nil, + locationTimeExclusivities: LocationTimeExclusivities? = nil, + locationTravelDurations: LocationTravelDurations? = nil, + balanceTimeStrictness: BalanceStrictness, + balancedTimes: [TimeIndex], + balanceLocationStrictness: BalanceStrictness, + balancedLocations: [LocationIndex], + flags: UInt32 + ) { + self.gameGap = gameGap + self.timeSlots = timeSlots + self.startingTimes = .init(times: startingTimes) + self.entriesPerLocation = entriesPerLocation + self.locations = locations + self.entryMatchupsPerGameDay = entryMatchupsPerGameDay + self.maximumPlayableMatchups = .init(array: maximumPlayableMatchups) + if let matchupDuration { + self.matchupDuration = matchupDuration + } + if let locationTimeExclusivities { + self.locationTimeExclusivities = locationTimeExclusivities + } + if let locationTravelDurations { + self.locationTravelDurations = locationTravelDurations + } + self.balanceTimeStrictness = balanceTimeStrictness + self.balancedTimes = .init(array: balancedTimes) + self.balanceLocationStrictness = balanceLocationStrictness + self.balancedLocations = .init(array: balancedLocations) + self.flags = flags + } +} + +// MARK: General +extension GeneralSettings { + func isFlag(_ flag: SettingFlags) -> Bool { + flags & UInt32(1 << flag.rawValue) != 0 + } + + /// If we should try arranging matchups so they fit in less time slots than provided. + var optimizeTimes: Bool { + isFlag(.optimizeTimes) + } + + /// If we should try arranging matchups so they fill earlier time slots first. + var prioritizeEarlierTimes: Bool { + isFlag(.prioritizeEarlierTimes) + } + + /// If we should try keeping matchups that play at "home" to play at "home" for later matchups (and the same for "away" matchups). + var prioritizeHomeAway: Bool { + isFlag(.prioritizeHomeAway) + } + + /// If we should try balancing the number each entry plays each other at "home" and "away". + var balanceHomeAway: Bool { + isFlag(.balanceHomeAway) + } + + /// If we should try keeping teams on the same location if they play back-to-back matchups. + var sameLocationIfB2B: Bool { + isFlag(.sameLocationIfBackToBack) + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueGameDayLocations+Extensions.swift b/Sources/league-scheduling/generated/extensions/LeagueGameDayLocations+Extensions.swift deleted file mode 100644 index b2b276f..0000000 --- a/Sources/league-scheduling/generated/extensions/LeagueGameDayLocations+Extensions.swift +++ /dev/null @@ -1,22 +0,0 @@ - -// MARK: Codable -extension LitLeagues_Leagues_GameDayLocations: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - locations = try container.decode([LeagueGameLocations].self) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(locations) - } -} - -// MARK: Init -extension LitLeagues_Leagues_GameDayLocations { - public init( - locations: [LeagueGameLocations] - ) { - self.locations = locations - } -} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueGameLocations+Extensions.swift b/Sources/league-scheduling/generated/extensions/LeagueGameLocations+Extensions.swift deleted file mode 100644 index 255c8c3..0000000 --- a/Sources/league-scheduling/generated/extensions/LeagueGameLocations+Extensions.swift +++ /dev/null @@ -1,21 +0,0 @@ - - -// MARK: Codable -extension LeagueGameLocations: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - locations = try container.decode([LeagueLocationIndex].self) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(locations) - } -} - -// MARK: General -extension LeagueGameLocations { - var set: Set { - Set(locations) - } -} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueMatchup+Extensions.swift b/Sources/league-scheduling/generated/extensions/LeagueMatchup+Extensions.swift deleted file mode 100644 index 2499234..0000000 --- a/Sources/league-scheduling/generated/extensions/LeagueMatchup+Extensions.swift +++ /dev/null @@ -1,57 +0,0 @@ - -// MARK: Codable -extension LeagueMatchup: Codable { - - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - time = try container.decode(LeagueTimeIndex.self, forKey: .time) - location = try container.decode(LeagueLocationIndex.self, forKey: .location) - home = try container.decode(LeagueEntry.IDValue.self, forKey: .home) - away = try container.decode(LeagueEntry.IDValue.self, forKey: .away) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(time, forKey: .time) - try container.encode(location, forKey: .location) - try container.encode(home, forKey: .home) - try container.encode(away, forKey: .away) - } - - public enum CodingKeys: CodingKey { - case time - case location - case home - case away - } -} - -// MARK: CustomStringConverible -extension LeagueMatchup: CustomStringConvertible { - public var description: String { - "T\(time)L\(location) \(away) @ \(home)" - } -} - -// MARK: General -extension LeagueMatchup { - public init( - time: LeagueTimeIndex, - location: LeagueLocationIndex, - home: LeagueEntry.IDValue, - away: LeagueEntry.IDValue - ) { - self.time = time - self.location = location - self.home = home - self.away = away - } - - public var pair: LeagueMatchupPair { - LeagueMatchupPair(team1: home, team2: away) - } - - public var slot: LeagueAvailableSlot { - LeagueAvailableSlot(time: time, location: location) - } -} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueMatchupOccurrence+Extensions.swift b/Sources/league-scheduling/generated/extensions/LeagueMatchupOccurrence+Extensions.swift deleted file mode 100644 index fff3202..0000000 --- a/Sources/league-scheduling/generated/extensions/LeagueMatchupOccurrence+Extensions.swift +++ /dev/null @@ -1,17 +0,0 @@ - -import StaticDateTimes - -// MARK: Codable -extension LeagueMatchupOccurrence: Codable { -} - -// MARK: General -extension LeagueMatchupOccurrence { - public var interval: LeagueMatchupDuration { - switch self { - case .daily: LeagueMatchupDuration.days(1) - case .weekly: LeagueMatchupDuration.weeks(1) - case .UNRECOGNIZED: LeagueMatchupDuration.days(1) - } - } -} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/Matchup+Extensions.swift b/Sources/league-scheduling/generated/extensions/Matchup+Extensions.swift new file mode 100644 index 0000000..37b6d15 --- /dev/null +++ b/Sources/league-scheduling/generated/extensions/Matchup+Extensions.swift @@ -0,0 +1,30 @@ + +// MARK: CustomStringConverible +extension Matchup: CustomStringConvertible { + public var description: String { + "T\(time)L\(location) \(away) @ \(home)" + } +} + +// MARK: General +extension Matchup { + init( + time: TimeIndex, + location: LocationIndex, + home: Entry.IDValue, + away: Entry.IDValue + ) { + self.time = time + self.location = location + self.home = home + self.away = away + } + + var pair: MatchupPair { + MatchupPair(team1: home, team2: away) + } + + var slot: AvailableSlot { + AvailableSlot(time: time, location: location) + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/MatchupPair+Extensions.swift b/Sources/league-scheduling/generated/extensions/MatchupPair+Extensions.swift index 03054d9..b3a2f1e 100644 --- a/Sources/league-scheduling/generated/extensions/MatchupPair+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/MatchupPair+Extensions.swift @@ -1,33 +1,13 @@ -// MARK: Codable -extension LeagueMatchupPair: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - team1 = try container.decode(LeagueEntry.IDValue.self, forKey: .team1) - team2 = try container.decode(LeagueEntry.IDValue.self, forKey: .team2) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - try container.encode(team1, forKey: .team1) - try container.encode(team2, forKey: .team2) - } - - public enum CodingKeys: CodingKey { - case team1 - case team2 - } -} - // MARK: CustomStringConvertible -extension LeagueMatchupPair: CustomStringConvertible { +extension MatchupPair: CustomStringConvertible { public var description: String { "\(team2) @ \(team1)" } } // MARK: Hashable -extension LeagueMatchupPair: Hashable { +extension MatchupPair: Hashable { public func hash(into hasher: inout Hasher) { hasher.combine(team1) hasher.combine(team2) @@ -35,8 +15,8 @@ extension LeagueMatchupPair: Hashable { } // MARK: Initializer -extension LeagueMatchupPair { - public init(team1: LeagueEntry.IDValue, team2: LeagueEntry.IDValue) { +extension MatchupPair { + init(team1: Entry.IDValue, team2: Entry.IDValue) { self.team1 = team1 self.team2 = team2 } diff --git a/Sources/league-scheduling/generated/extensions/RedistributionSettings+Extensions.swift b/Sources/league-scheduling/generated/extensions/RedistributionSettings+Extensions.swift index 25776af..7ed0e2a 100644 --- a/Sources/league-scheduling/generated/extensions/RedistributionSettings+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/RedistributionSettings+Extensions.swift @@ -1,43 +1,14 @@ -// MARK: Codable -extension LitLeagues_Leagues_RedistributionSettings: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - if let v = try container.decodeIfPresent(UInt32.self, forKey: .minMatchupsRequired) { - minMatchupsRequired = v - } - if let v = try container.decodeIfPresent(UInt32.self, forKey: .maxMovableMatchups) { - maxMovableMatchups = v - } - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - if hasMinMatchupsRequired { - try container.encode(minMatchupsRequired, forKey: .minMatchupsRequired) - } - if hasMaxMovableMatchups { - try container.encode(maxMovableMatchups, forKey: .maxMovableMatchups) - } - } - - enum CodingKeys: CodingKey { - case minMatchupsRequired - case maxMovableMatchups - } -} - -// MARK: Init extension LitLeagues_Leagues_RedistributionSettings { init( minMatchupsRequired: UInt32? = nil, maxMovableMatchups: UInt32? = nil ) { - if let m = minMatchupsRequired { - self.minMatchupsRequired = m + if let v = minMatchupsRequired { + self.minMatchupsRequired = v } - if let m = maxMovableMatchups { - self.maxMovableMatchups = m + if let v = maxMovableMatchups { + self.maxMovableMatchups = v } } } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift b/Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift new file mode 100644 index 0000000..2b28731 --- /dev/null +++ b/Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift @@ -0,0 +1,27 @@ + +extension RequestPayload { + init( + starts: String? = nil, + gameDays: DayIndex, + + settings: GeneralSettings, + individualDaySettings: LitLeagues_Leagues_DaySettingsArray?, + + divisions: [Division], + teams: [Entry] + ) { + if let starts { + self.starts = starts + } + + self.gameDays = gameDays + + self.settings = settings + if let individualDaySettings { + self.individualDaySettings = individualDaySettings + } + + self.divisions = .init(divisions: divisions) + self.entries = teams + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift similarity index 84% rename from Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift rename to Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift index 79adef5..958b564 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift @@ -8,7 +8,7 @@ import Foundation #endif // MARK: Generate -extension LeagueRequestPayload { +extension RequestPayload { public func generate() async throws(LeagueError) -> LeagueGenerationResult { guard gameDays > 0 else { throw .malformedInput(msg: "'gameDays' needs to be > 0") @@ -40,7 +40,7 @@ extension LeagueRequestPayload { } } } - let startingDayOfWeek:LeagueDayOfWeek + let startingDayOfWeek:DayOfWeek if hasStarts { guard let starts = StaticDate(htmlDate: starts) else { throw .malformedHTMLDateInput(key: "starts", value: starts) @@ -59,11 +59,11 @@ extension LeagueRequestPayload { } } -extension LeagueRequestPayload { +extension RequestPayload { private func generate( defaultGameGap: GameGap, divisionsCount: Int, - startingDayOfWeek: LeagueDayOfWeek + startingDayOfWeek: DayOfWeek ) async throws(LeagueError) -> LeagueGenerationResult { if gameDays <= 64 && settings.timeSlots <= 64 && settings.locations <= 64 && entries.count <= 64 { return try await generate( @@ -71,10 +71,10 @@ extension LeagueRequestPayload { divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, c: ScheduleConfig< - BitSet64, - BitSet64, - BitSet64, - BitSet64 + BitSet64, + BitSet64, + BitSet64, + BitSet64 >.self ) } else if gameDays <= 128 && settings.timeSlots <= 128 && settings.locations <= 128 && entries.count <= 128 { @@ -83,10 +83,10 @@ extension LeagueRequestPayload { divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, c: ScheduleConfig< - BitSet128, - BitSet128, - BitSet128, - BitSet128 + BitSet128, + BitSet128, + BitSet128, + BitSet128 >.self ) } else { @@ -95,21 +95,21 @@ extension LeagueRequestPayload { divisionsCount: divisionsCount, startingDayOfWeek: startingDayOfWeek, c: ScheduleConfig< - Set, - Set, - Set, - Set + Set, + Set, + Set, + Set >.self ) } } } -extension LeagueRequestPayload { +extension RequestPayload { private func generate( defaultGameGap: GameGap, divisionsCount: Int, - startingDayOfWeek: LeagueDayOfWeek, + startingDayOfWeek: DayOfWeek, c: Config.Type ) async throws(LeagueError) -> LeagueGenerationResult { let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) @@ -144,7 +144,7 @@ extension LeagueRequestPayload { } } } - var defaultTravelDurations:[[LeagueMatchupDuration]] = .init(repeating: .init(repeating: 0, count: settings.locations), count: settings.locations) + var defaultTravelDurations:[[MatchupDuration]] = .init(repeating: .init(repeating: 0, count: settings.locations), count: settings.locations) if settings.hasLocationTravelDurations { for (fromLocation, values) in settings.locationTravelDurations.locations.enumerated() { if !values.travelDurationTo.isEmpty { @@ -168,7 +168,7 @@ extension LeagueRequestPayload { divisions: divisions, entries: entries, correctMaximumPlayableMatchups: correctMaximumPlayableMatchups, - general: LeagueGeneralSettings.Runtime( + general: GeneralSettings.Runtime( gameGap: defaultGameGap, timeSlots: settings.timeSlots, startingTimes: settings.startingTimes.times, @@ -190,34 +190,36 @@ extension LeagueRequestPayload { } } -extension LeagueRequestPayload { +extension RequestPayload { #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif private func generate( divisions: [Config.DivisionRuntime], entries: [Config.EntryRuntime], correctMaximumPlayableMatchups: [UInt32], - general: LeagueGeneralSettings.Runtime + general: GeneralSettings.Runtime ) async throws(LeagueError) -> LeagueGenerationResult { let daySettings = try parseDaySettings( general: general, correctMaximumPlayableMatchups: correctMaximumPlayableMatchups, entries: entries ) - return await LeagueSchedule.generate(LeagueRequestPayload.Runtime( + let runtime = RequestPayload.Runtime( + constraints: generationConstraints, gameDays: gameDays, divisions: divisions, entries: entries, general: general, daySettings: daySettings - )) + ) + return await runtime.generate() } } // MARK: Division defaults -extension LeagueRequestPayload { +extension RequestPayload { struct DivisionDefaults: Sendable, ~Copyable { let gameDays:[DaySet] let byes:[DaySet] @@ -227,7 +229,7 @@ extension LeagueRequestPayload { } // MARK: Load division defaults -extension LeagueRequestPayload { +extension RequestPayload { private func loadDivisionDefaults< DaySet: SetOfDayIndexes, Times: SetOfTimeIndexes, @@ -244,8 +246,8 @@ extension LeagueRequestPayload { gameTimes.reserveCapacity(divisionsCount) gameLocations.reserveCapacity(divisionsCount) - let getTimesFunc:@Sendable (LeagueRequestPayload, LeagueDayIndex, LeagueTimeIndex) -> LeagueTimeIndex - let getLocationsFunc:@Sendable (LeagueRequestPayload, LeagueDayIndex, LeagueLocationIndex) -> LeagueLocationIndex + let getTimesFunc:@Sendable (RequestPayload, DayIndex, TimeIndex) -> TimeIndex + let getLocationsFunc:@Sendable (RequestPayload, DayIndex, LocationIndex) -> LocationIndex if hasIndividualDaySettings { getTimesFunc = Self.individualDayTimes getLocationsFunc = Self.individualDayLocations @@ -314,34 +316,34 @@ extension LeagueRequestPayload { ) } private static func individualDayTimes( - payload: LeagueRequestPayload, - dayIndex: LeagueDayIndex, - fallback: LeagueTimeIndex - ) -> LeagueTimeIndex { + payload: RequestPayload, + dayIndex: DayIndex, + fallback: TimeIndex + ) -> TimeIndex { let daySettings = payload.individualDaySettings.days[unchecked: dayIndex] guard daySettings.hasSettings, daySettings.settings.hasTimeSlots else { return fallback } return daySettings.settings.timeSlots } private static func individualDayLocations( - payload: LeagueRequestPayload, - dayIndex: LeagueDayIndex, - fallback: LeagueLocationIndex - ) -> LeagueLocationIndex { + payload: RequestPayload, + dayIndex: DayIndex, + fallback: LocationIndex + ) -> LocationIndex { let daySettings = payload.individualDaySettings.days[unchecked: dayIndex] guard daySettings.hasSettings, daySettings.settings.hasLocations else { return fallback } - return LeagueTimeIndex(daySettings.settings.locations) + return TimeIndex(daySettings.settings.locations) } } // MARK: Parse teams -extension LeagueRequestPayload { +extension RequestPayload { private func parseEntries( divisionsCount: Int, - teams: [LeagueEntry], + teams: [Entry], teamsForDivision: inout [Int], divisionDefaults: borrowing DivisionDefaults - ) throws(LeagueError) -> [LeagueEntry.Runtime] { - var entries = [LeagueEntry.Runtime]() + ) throws(LeagueError) -> [Entry.Runtime] { + var entries = [Entry.Runtime]() entries.reserveCapacity(teams.count) for (i, team) in teams.enumerated() { if team.hasGameDayTimes { @@ -362,7 +364,7 @@ extension LeagueRequestPayload { let division = min(team.division, UInt32(divisionsCount - 1)) teamsForDivision[Int(division)] += 1 entries.append(team.runtime( - id: LeagueEntry.IDValue(i), + id: Entry.IDValue(i), division: division, defaultGameDays: divisionDefaults.gameDays[unchecked: division], defaultByes: divisionDefaults.byes[unchecked: division], @@ -375,16 +377,16 @@ extension LeagueRequestPayload { } // MARK: Parse divisions -extension LeagueRequestPayload { +extension RequestPayload { private func parseDivisions( divisionsCount: Int, - locations: LeagueLocationIndex, + locations: LocationIndex, divisionGameDays: [DaySet], defaultGameGap: GameGap, - fallbackDayOfWeek: LeagueDayOfWeek, + fallbackDayOfWeek: DayOfWeek, teamsForDivision: [Int] - ) throws(LeagueError) -> [LeagueDivision.Runtime] { - var runtimeDivisions = [LeagueDivision.Runtime]() + ) throws(LeagueError) -> [Division.Runtime] { + var runtimeDivisions = [Division.Runtime]() runtimeDivisions.reserveCapacity(divisionsCount) if hasDivisions { for (i, division) in divisions.divisions.enumerated() { @@ -435,17 +437,17 @@ extension LeagueRequestPayload { } // MARK: Parse day settings -extension LeagueRequestPayload { +extension RequestPayload { #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif private func parseDaySettings( - general: LeagueGeneralSettings.Runtime, + general: GeneralSettings.Runtime, correctMaximumPlayableMatchups: [UInt32], entries: [Config.EntryRuntime] - ) throws(LeagueError) -> [LeagueGeneralSettings.Runtime] { - var daySettings = [LeagueGeneralSettings.Runtime]() + ) throws(LeagueError) -> [GeneralSettings.Runtime] { + var daySettings = [GeneralSettings.Runtime]() daySettings.reserveCapacity(gameDays) if hasIndividualDaySettings { for dayIndex in 0.. LeagueMaximumSameOpponentMatchupsCap { + ) throws(LeagueError) -> MaximumSameOpponentMatchupsCap { return try Self.calculateMaximumSameOpponentMatchupsCap( gameDays: gameDays, entryMatchupsPerGameDay: settings.entryMatchupsPerGameDay, @@ -486,14 +488,14 @@ extension LeagueRequestPayload { } static func calculateMaximumSameOpponentMatchupsCap( - gameDays: LeagueDayIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, + gameDays: DayIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, entriesCount: Int - ) throws(LeagueError) -> LeagueMaximumSameOpponentMatchupsCap { + ) throws(LeagueError) -> MaximumSameOpponentMatchupsCap { guard entriesCount > 1 else { throw .malformedInput(msg: "Number of teams need to be > 1 when calculating maximum same opponent matchups cap; got \(entriesCount)") } - return LeagueMaximumSameOpponentMatchupsCap( + return MaximumSameOpponentMatchupsCap( ceil( Double(gameDays) / (Double(entriesCount-1) / Double(entryMatchupsPerGameDay)) ) @@ -502,10 +504,10 @@ extension LeagueRequestPayload { } // MARK: Calculate max playable matchups -extension LeagueRequestPayload { +extension RequestPayload { static func calculateMaximumPlayableMatchups( - gameDays: LeagueDayIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, + gameDays: DayIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, teamsCount: Int, maximumPlayableMatchups: [UInt32] ) -> [UInt32] { diff --git a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Validate.swift b/Sources/league-scheduling/generated/extensions/RequestPayload+Validate.swift similarity index 96% rename from Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Validate.swift rename to Sources/league-scheduling/generated/extensions/RequestPayload+Validate.swift index 01fdd22..6bfb596 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueRequestPayload+Validate.swift +++ b/Sources/league-scheduling/generated/extensions/RequestPayload+Validate.swift @@ -1,10 +1,10 @@ -extension LeagueRequestPayload { +extension RequestPayload { @discardableResult func validateSettings( kind: String, - settings: LeagueGeneralSettings, - fallbackSettings: LeagueGeneralSettings + settings: GeneralSettings, + fallbackSettings: GeneralSettings ) throws(LeagueError) -> GameGap? { let isDefault = kind == "default" if isDefault || settings.hasTimeSlots { diff --git a/Sources/league-scheduling/generated/extensions/LeagueSettingFlags+Extensions.swift b/Sources/league-scheduling/generated/extensions/SettingFlags+Extensions.swift similarity index 89% rename from Sources/league-scheduling/generated/extensions/LeagueSettingFlags+Extensions.swift rename to Sources/league-scheduling/generated/extensions/SettingFlags+Extensions.swift index f955f99..83bf1c9 100644 --- a/Sources/league-scheduling/generated/extensions/LeagueSettingFlags+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/SettingFlags+Extensions.swift @@ -1,7 +1,6 @@ -// MARK: General -extension LeagueSettingFlags { - public static func get( +extension SettingFlags { + static func get( optimizeTimes: Bool, prioritizeEarlierTimes: Bool, prioritizeHomeAway: Bool, diff --git a/Sources/league-scheduling/generated/extensions/StaticTimes+Extensions.swift b/Sources/league-scheduling/generated/extensions/StaticTimes+Extensions.swift index bfa95c8..6c6c8e2 100644 --- a/Sources/league-scheduling/generated/extensions/StaticTimes+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/StaticTimes+Extensions.swift @@ -1,20 +1,6 @@ import StaticDateTimes -// MARK: Codable -extension LitLeagues_Leagues_StaticTimes: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - times = try container.decode([StaticTime].self) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(times) - } -} - -// MARK: Init extension LitLeagues_Leagues_StaticTimes { init(times: [StaticTime]) { self.times = times diff --git a/Sources/league-scheduling/generated/extensions/UInt32Array+Extensions.swift b/Sources/league-scheduling/generated/extensions/UInt32Array+Extensions.swift index 4d6138d..859ebdb 100644 --- a/Sources/league-scheduling/generated/extensions/UInt32Array+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/UInt32Array+Extensions.swift @@ -1,18 +1,4 @@ -// MARK: Codablee -extension LitLeagues_Leagues_UInt32Array: Codable { - public init(from decoder: any Decoder) throws { - let container = try decoder.singleValueContainer() - array = try container.decode([UInt32].self) - } - - public func encode(to encoder: any Encoder) throws { - var container = encoder.singleValueContainer() - try container.encode(array) - } -} - -// MARK: Init extension LitLeagues_Leagues_UInt32Array { init(array: [UInt32]) { self.array = array diff --git a/Sources/league-scheduling/generated/runtime/LeagueDivision+Runtime.swift b/Sources/league-scheduling/generated/runtime/Division+Runtime.swift similarity index 73% rename from Sources/league-scheduling/generated/runtime/LeagueDivision+Runtime.swift rename to Sources/league-scheduling/generated/runtime/Division+Runtime.swift index 2fc8ce5..49d2ee3 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueDivision+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/Division+Runtime.swift @@ -1,10 +1,10 @@ -extension LeagueDivision { +extension Division { func runtime( defaultGameDays: DaySet, defaultGameGap: GameGap, - fallbackDayOfWeek: LeagueDayOfWeek, - fallbackMaxSameOpponentMatchups: LeagueMaximumSameOpponentMatchupsCap + fallbackDayOfWeek: DayOfWeek, + fallbackMaxSameOpponentMatchups: MaximumSameOpponentMatchupsCap ) throws(LeagueError) -> Runtime { try .init( protobuf: self, @@ -17,29 +17,29 @@ extension LeagueDivision { /// For optimal runtime performance. struct Runtime: Sendable { - let dayOfWeek:LeagueDayOfWeek + let dayOfWeek:DayOfWeek let gameDays:DaySet let gameGaps:[GameGap] - let maxSameOpponentMatchups:LeagueMaximumSameOpponentMatchupsCap + let maxSameOpponentMatchups:MaximumSameOpponentMatchupsCap init( - protobuf: LeagueDivision, + protobuf: Division, defaultGameDays: DaySet, defaultGameGap: GameGap, - fallbackDayOfWeek: LeagueDayOfWeek, - fallbackMaxSameOpponentMatchups: LeagueMaximumSameOpponentMatchupsCap + fallbackDayOfWeek: DayOfWeek, + fallbackMaxSameOpponentMatchups: MaximumSameOpponentMatchupsCap ) throws(LeagueError) { - dayOfWeek = protobuf.hasDayOfWeek ? LeagueDayOfWeek(protobuf.dayOfWeek) : fallbackDayOfWeek + dayOfWeek = protobuf.hasDayOfWeek ? DayOfWeek(protobuf.dayOfWeek) : fallbackDayOfWeek self.gameDays = protobuf.hasGameDays ? .init(protobuf.gameDays.gameDayIndexes) : defaultGameDays gameGaps = protobuf.hasGameGaps ? try Self.parseGameGaps(protobuf.gameGaps.gameGaps) : .init(repeating: defaultGameGap, count: defaultGameDays.count) maxSameOpponentMatchups = protobuf.hasMaxSameOpponentMatchups ? protobuf.maxSameOpponentMatchups : fallbackMaxSameOpponentMatchups } init( - dayOfWeek: LeagueDayOfWeek, + dayOfWeek: DayOfWeek, gameDays: DaySet, gameGaps: [GameGap], - maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchupsCap + maxSameOpponentMatchups: MaximumSameOpponentMatchupsCap ) { self.dayOfWeek = dayOfWeek self.gameDays = gameDays @@ -50,7 +50,7 @@ extension LeagueDivision { } // MARK: Parse game gaps -extension LeagueDivision.Runtime { +extension Division.Runtime { private static func parseGameGaps(_ gameGaps: [String]) throws(LeagueError) -> [GameGap] { var gaps = [GameGap]() gaps.reserveCapacity(gameGaps.count) diff --git a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift b/Sources/league-scheduling/generated/runtime/Entry+Runtime.swift similarity index 83% rename from Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift rename to Sources/league-scheduling/generated/runtime/Entry+Runtime.swift index f0920a4..3e80de8 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueEntry+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/Entry+Runtime.swift @@ -1,8 +1,8 @@ -extension LeagueEntry { +extension Entry { func runtime( id: IDValue, - division: LeagueDivision.IDValue, + division: Division.IDValue, defaultGameDays: DaySet, defaultByes: DaySet, defaultGameTimes: [Times], @@ -21,22 +21,22 @@ extension LeagueEntry { struct Runtime: Sendable { /// ID associated with this entry. - let id:LeagueEntry.IDValue + let id:Entry.IDValue /// Division id this entry is in. - let division:LeagueDivision.IDValue + let division:Division.IDValue /// Game days this entry can play on. let gameDays:DaySet /// Times this entry can play at for a specific day index. /// - /// - Usage: [`LeagueDayIndex`: `Set`] + /// - Usage: [`DayIndex`: `Set`] let gameTimes:[TimeSet] /// Locations this entry can play at for a specific day index. /// - /// - Usage: [`LeagueDayIndex`: `Set`] + /// - Usage: [`DayIndex`: `Set`] let gameLocations:[LocationSet] /// Home locations for this entry. @@ -48,9 +48,9 @@ extension LeagueEntry { let matchupsPerGameDay:LitLeagues_Leagues_EntryMatchupsPerGameDay? init( - id: LeagueEntry.IDValue, - division: LeagueDivision.IDValue, - protobuf: LeagueEntry, + id: Entry.IDValue, + division: Division.IDValue, + protobuf: Entry, defaultGameDays: DaySet, defaultByes: DaySet, defaultGameTimes: [TimeSet], @@ -67,8 +67,8 @@ extension LeagueEntry { } init( - id: LeagueEntry.IDValue, - division: LeagueDivision.IDValue, + id: Entry.IDValue, + division: Division.IDValue, gameDays: DaySet, gameTimes: [TimeSet], gameLocations: [LocationSet], @@ -88,9 +88,9 @@ extension LeagueEntry { /// - Returns: Maximum number of matchups this entry can play on the given day index. func maxMatchupsForGameDay( - day: LeagueDayIndex, - fallback: LeagueEntryMatchupsPerGameDay - ) -> LeagueEntryMatchupsPerGameDay { + day: DayIndex, + fallback: EntryMatchupsPerGameDay + ) -> EntryMatchupsPerGameDay { return matchupsPerGameDay?.gameDayMatchups[uncheckedPositive: day] ?? fallback } } diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift b/Sources/league-scheduling/generated/runtime/GeneralSettings+Runtime.swift similarity index 78% rename from Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift rename to Sources/league-scheduling/generated/runtime/GeneralSettings+Runtime.swift index cae2fea..b077609 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+Runtime.swift +++ b/Sources/league-scheduling/generated/runtime/GeneralSettings+Runtime.swift @@ -1,31 +1,31 @@ import StaticDateTimes -extension LeagueGeneralSettings { +extension GeneralSettings { struct Runtime: Sendable { var gameGap:GameGap - var timeSlots:LeagueTimeIndex + var timeSlots:TimeIndex var startingTimes:[StaticTime] - var entriesPerLocation:LeagueEntriesPerMatchup - var locations:LeagueLocationIndex - var defaultMaxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay + var entriesPerLocation:EntriesPerMatchup + var locations:LocationIndex + var defaultMaxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay var maximumPlayableMatchups:[UInt32] - var matchupDuration:LeagueMatchupDuration + var matchupDuration:MatchupDuration var locationTimeExclusivities:[Config.TimeSet]? - var locationTravelDurations:[[LeagueMatchupDuration]]? - var balanceTimeStrictness:LeagueBalanceStrictness + var locationTravelDurations:[[MatchupDuration]]? + var balanceTimeStrictness:BalanceStrictness var balancedTimes:Config.TimeSet - var balanceLocationStrictness:LeagueBalanceStrictness + var balanceLocationStrictness:BalanceStrictness var balancedLocations:Config.LocationSet var redistributionSettings:LitLeagues_Leagues_RedistributionSettings? var flags:UInt32 } } -extension LeagueGeneralSettings.Runtime { +extension GeneralSettings.Runtime { init( gameGap: GameGap, - protobuf: LeagueGeneralSettings + protobuf: GeneralSettings ) { self.gameGap = gameGap timeSlots = protobuf.timeSlots @@ -58,11 +58,11 @@ extension LeagueGeneralSettings.Runtime { } mutating func apply( - gameDays: LeagueDayIndex, + gameDays: DayIndex, entriesCount: Int, correctMaximumPlayableMatchups: [UInt32], general: Self, - customDaySettings: LeagueGeneralSettings + customDaySettings: GeneralSettings ) { if customDaySettings.hasGameGap, let gg = GameGap(htmlInputValue: customDaySettings.gameGap) { self.gameGap = gg @@ -83,7 +83,7 @@ extension LeagueGeneralSettings.Runtime { self.defaultMaxEntryMatchupsPerGameDay = customDaySettings.entryMatchupsPerGameDay } if customDaySettings.hasMaximumPlayableMatchups { - self.maximumPlayableMatchups = LeagueRequestPayload.calculateMaximumPlayableMatchups( + self.maximumPlayableMatchups = RequestPayload.calculateMaximumPlayableMatchups( gameDays: gameDays, entryMatchupsPerGameDay: self.defaultMaxEntryMatchupsPerGameDay, teamsCount: entriesCount, @@ -123,14 +123,14 @@ extension LeagueGeneralSettings.Runtime { } } - func availableSlots() -> Set { - var slots = Set(minimumCapacity: timeSlots * locations) + func availableSlots() -> Set { + var slots = Set(minimumCapacity: timeSlots * locations) if let exclusivities = locationTimeExclusivities { for location in 0.. Bool { + func containsBalancedTime(_ timeSlot: TimeIndex) -> Bool { balancedTimes.contains(timeSlot) } - func containsBalancedLocation(_ location: LeagueLocationIndex) -> Bool { + func containsBalancedLocation(_ location: LocationIndex) -> Bool { balancedLocations.contains(location) } #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) #endif init( gameGap: GameGap, - timeSlots: LeagueTimeIndex, + timeSlots: TimeIndex, startingTimes: [StaticTime], - entriesPerLocation: LeagueEntriesPerMatchup, - locations: LeagueLocationIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, + entriesPerLocation: EntriesPerMatchup, + locations: LocationIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, maximumPlayableMatchups: [UInt32], - matchupDuration: LeagueMatchupDuration, + matchupDuration: MatchupDuration, locationTimeExclusivities: [Config.TimeSet]?, - locationTravelDurations: [[LeagueMatchupDuration]]?, - balanceTimeStrictness: LeagueBalanceStrictness, + locationTravelDurations: [[MatchupDuration]]?, + balanceTimeStrictness: BalanceStrictness, balancedTimes: Config.TimeSet, - balanceLocationStrictness: LeagueBalanceStrictness, + balanceLocationStrictness: BalanceStrictness, balancedLocations: Config.LocationSet, redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, flags: UInt32 diff --git a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift b/Sources/league-scheduling/generated/runtime/GeneralSettings+RuntimeProtocol.swift similarity index 86% rename from Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift rename to Sources/league-scheduling/generated/runtime/GeneralSettings+RuntimeProtocol.swift index 7620abe..010d492 100644 --- a/Sources/league-scheduling/generated/runtime/LeagueGeneralSettings+RuntimeProtocol.swift +++ b/Sources/league-scheduling/generated/runtime/GeneralSettings+RuntimeProtocol.swift @@ -2,8 +2,8 @@ import StaticDateTimes // MARK: Flags -extension LeagueGeneralSettings.Runtime { - func isFlag(_ flag: LeagueSettingFlags) -> Bool { +extension GeneralSettings.Runtime { + func isFlag(_ flag: SettingFlags) -> Bool { flags & UInt32(1 << flag.rawValue) != 0 } @@ -29,9 +29,9 @@ extension LeagueGeneralSettings.Runtime { } // MARK: Compute settings -extension LeagueGeneralSettings.Runtime { +extension GeneralSettings.Runtime { init( - protobuf: LeagueGeneralSettings + protobuf: GeneralSettings ) throws(LeagueError) { guard let gameGap = GameGap(htmlInputValue: protobuf.gameGap) else { throw .malformedInput(msg: "invalid GameGap htmlInputValue: \(protobuf.gameGap)") @@ -41,11 +41,11 @@ extension LeagueGeneralSettings.Runtime { /// Modifies `timeSlots` and `startingTimes` taking into account current settings. mutating func computeSettings( - day: LeagueDayIndex, + day: DayIndex, entries: [Config.EntryRuntime] ) { if optimizeTimes { - var maxMatchupsPlayedToday:LeagueLocationIndex = 0 + var maxMatchupsPlayedToday:LocationIndex = 0 for entry in entries { if entry.gameDays.contains(day) && !entry.byes.contains(day) { maxMatchupsPlayedToday += entry.maxMatchupsForGameDay(day: day, fallback: defaultMaxEntryMatchupsPerGameDay) diff --git a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift deleted file mode 100644 index 28dc1ce..0000000 --- a/Sources/league-scheduling/generated/runtime/LeagueRequestPayload+Runtime.swift +++ /dev/null @@ -1,57 +0,0 @@ - -#if canImport(FoundationEssentials) -import struct FoundationEssentials.Date -#elseif canImport(Foundation) -import struct Foundation.Date -#endif - -import SwiftProtobuf - -// MARK: Runtime -extension LeagueRequestPayload { - /// For optimal runtime performance. - struct Runtime: Sendable, ~Copyable { - /// Number of days where games are played. - let gameDays:LeagueDayIndex - - /// Divisions associated with this schedule. - let divisions:[Config.DivisionRuntime] - - /// Entries that participate in this schedule. - let entries:[Config.EntryRuntime] - - /// General settings for this schedule. - let general:LeagueGeneralSettings.Runtime - - /// Individual settings for the given day index. - /// - /// - Usage: [`LeagueDayIndex`: `LeagueDaySettings`] - let daySettings:[LeagueGeneralSettings.Runtime] - - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif - init( - gameDays: LeagueDayIndex, - divisions: [Config.DivisionRuntime], - entries: [Config.EntryRuntime], - general: LeagueGeneralSettings.Runtime, - daySettings: [LeagueGeneralSettings.Runtime] - ) { - self.gameDays = gameDays - self.divisions = divisions - self.entries = entries - self.general = general - self.daySettings = daySettings - } - - func copy() -> Self { - .init(gameDays: gameDays, divisions: divisions, entries: entries, general: general, daySettings: daySettings) - } - - func redistributionSettings(for day: LeagueDayIndex) -> LitLeagues_Leagues_RedistributionSettings? { - daySettings[unchecked: day].redistributionSettings ?? general.redistributionSettings - } - } -} \ No newline at end of file diff --git a/Sources/league-scheduling/generated/runtime/RequestPayload+Runtime.swift b/Sources/league-scheduling/generated/runtime/RequestPayload+Runtime.swift new file mode 100644 index 0000000..eb797d5 --- /dev/null +++ b/Sources/league-scheduling/generated/runtime/RequestPayload+Runtime.swift @@ -0,0 +1,52 @@ + +extension RequestPayload { + /// For optimal runtime performance. + struct Runtime: Sendable, ~Copyable { + let constraints:GenerationConstraints + + /// Number of days where games are played. + let gameDays:DayIndex + + /// Divisions associated with this schedule. + let divisions:[Config.DivisionRuntime] + + /// Entries that participate in this schedule. + let entries:[Config.EntryRuntime] + + /// General settings for this schedule. + let general:GeneralSettings.Runtime + + /// Individual settings for the given day index. + /// + /// - Usage: [`DayIndex`: `DaySettings`] + let daySettings:[GeneralSettings.Runtime] + + #if SpecializeScheduleConfiguration + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif + init( + constraints: GenerationConstraints, + gameDays: DayIndex, + divisions: [Config.DivisionRuntime], + entries: [Config.EntryRuntime], + general: GeneralSettings.Runtime, + daySettings: [GeneralSettings.Runtime] + ) { + self.constraints = constraints + self.gameDays = gameDays + self.divisions = divisions + self.entries = entries + self.general = general + self.daySettings = daySettings + } + + func copy() -> Self { + .init(constraints: constraints, gameDays: gameDays, divisions: divisions, entries: entries, general: general, daySettings: daySettings) + } + + func redistributionSettings(for day: DayIndex) -> LitLeagues_Leagues_RedistributionSettings? { + daySettings[unchecked: day].redistributionSettings ?? general.redistributionSettings + } + } +} \ No newline at end of file diff --git a/Sources/league-scheduling/globals.swift b/Sources/league-scheduling/globals.swift new file mode 100644 index 0000000..39e6d6b --- /dev/null +++ b/Sources/league-scheduling/globals.swift @@ -0,0 +1,40 @@ + +func optimalTimeSlots( + availableTimeSlots: TimeIndex, + locations: LocationIndex, + matchupsCount: LocationIndex +) -> TimeIndex { + var totalMatchupsPlayed:LocationIndex = 0 + var filledTimes:TimeIndex = 0 + while totalMatchupsPlayed < matchupsCount { + filledTimes += 1 + totalMatchupsPlayed += locations + } + #if LOG + print("LeagueSchedule;optimalTimeSlots;availableTimeSlots=\(availableTimeSlots);locations=\(locations);matchupsCount=\(matchupsCount);totalMatchupsPlayed=\(totalMatchupsPlayed);filledTimes=\(filledTimes)") + #endif + return min(availableTimeSlots, filledTimes) +} + +func calculateAdjacentTimes( + for time: TimeIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay +) -> TimeSet { + var adjacentTimes = TimeSet() + let timeIndex = time % entryMatchupsPerGameDay + if timeIndex == 0 { + for i in 1..> + +/// Maximum number of times an entry can play against another entry. +/// +/// - Usage: [`Entry.IDValue`: [opponent `Entry.IDValue`: `maximum allowed matchups for opponent`]] +typealias MaximumSameOpponentMatchups = ContiguousArray> +typealias MaximumSameOpponentMatchupsCap = UInt32 + +/// Remaining allocations allowed for a matchup pair, for a `DayIndex`. +/// +/// - Usage: [`Entry.IDValue`: `the number of remaining allocations`] +typealias RemainingAllocations = ContiguousArray> + +/// When entries can play against each other again. +/// +/// - Usage: [`Entry.IDValue`: [opponent `Entry.IDValue`: `RecurringDayLimitInterval`]] +typealias RecurringDayLimits = ContiguousArray> + +/// Number of times an entry was assigned to a given time. +/// +/// - Usage: [`Entry.IDValue`: [`TimeIndex`: `amount played at TimeIndex`] +typealias AssignedTimes = ContiguousArray> + +/// Number of times an entry was assigned to a given location. +/// +/// - Usage: [`Entry.IDValue`: [`LocationIndex`: `amount played at LocationIndex`]] +typealias AssignedLocations = ContiguousArray> + +/// Maximum number of allocations allowed for a given entry for a given time. +/// +/// - Usage: [`Entry.IDValue`: [`TimeIndex`: `maximum allowed at TimeIndex`]] +typealias MaximumTimeAllocations = ContiguousArray> + +/// Maximum number of allocations allowed for a given entry for a given location. +/// +/// - Usage: [`Entry.IDValue`: [`LocationIndex`: `maximum allowed at LocationIndex`]] +typealias MaximumLocationAllocations = ContiguousArray> + +/// Times where an entry has already played at for the `day`. +/// +/// - Usage: [`Entry.IDValue`: `Set`] +typealias PlaysAtTimes = ContiguousArray> + +/// Locations where an entry has already played at for the `day`. +/// +/// - Usage: [`Entry.IDValue`: `Set`] +typealias PlaysAtLocations = ContiguousArray> + +/// Slots where an entry has already played at for the `day`. +/// +/// - Usage: [`Entry.IDValue`: `Set`] +typealias PlaysAt = ContiguousArray> \ No newline at end of file diff --git a/Sources/league-scheduling/util/AbstractSet.swift b/Sources/league-scheduling/util/AbstractSet.swift index 6debfd1..b1f4489 100644 --- a/Sources/league-scheduling/util/AbstractSet.swift +++ b/Sources/league-scheduling/util/AbstractSet.swift @@ -32,16 +32,16 @@ protocol AbstractSet: Sendable, ~Copyable { func forEachWithReturn(_ body: (Element) throws -> Result?) rethrows -> Result? } -protocol SetOfDayIndexes: AbstractSet, ~Copyable where Element == LeagueDayIndex {} -protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == LeagueTimeIndex {} -protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LeagueLocationIndex {} +protocol SetOfDayIndexes: AbstractSet, ~Copyable where Element == DayIndex {} +protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == TimeIndex {} +protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LocationIndex {} -protocol SetOfEntryIDs: AbstractSet, ~Copyable where Element == LeagueEntry.IDValue { +protocol SetOfEntryIDs: AbstractSet, ~Copyable where Element == Entry.IDValue { /// - Returns: The available matchup pairs that can play for the `day`. func availableMatchupPairs( assignedEntryHomeAways: AssignedEntryHomeAways, - maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups - ) -> Set + maxSameOpponentMatchups: MaximumSameOpponentMatchups + ) -> Set } extension Set: AbstractSet { @@ -83,16 +83,16 @@ extension Set: AbstractSet { } } -extension Set: SetOfDayIndexes {} -extension Set: SetOfTimeIndexes {} -extension Set: SetOfLocationIndexes {} +extension Set: SetOfDayIndexes {} +extension Set: SetOfTimeIndexes {} +extension Set: SetOfLocationIndexes {} -extension Set: SetOfEntryIDs { +extension Set: SetOfEntryIDs { func availableMatchupPairs( assignedEntryHomeAways: AssignedEntryHomeAways, - maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups - ) -> Set { - var pairs = Set(minimumCapacity: (count-1) * 2) + maxSameOpponentMatchups: MaximumSameOpponentMatchups + ) -> Set { + var pairs = Set(minimumCapacity: (count-1) * 2) let sortedEntries = sorted() var index = 0 while index < sortedEntries.count - 1 { diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index 694dde9..c3e0502 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -110,16 +110,16 @@ extension BitSet128 { // MARK: AbstractSet extension BitSet128: AbstractSet {} -extension BitSet128: SetOfDayIndexes where Element == LeagueTimeIndex {} -extension BitSet128: SetOfTimeIndexes where Element == LeagueTimeIndex {} -extension BitSet128: SetOfLocationIndexes where Element == LeagueLocationIndex {} +extension BitSet128: SetOfDayIndexes where Element == TimeIndex {} +extension BitSet128: SetOfTimeIndexes where Element == TimeIndex {} +extension BitSet128: SetOfLocationIndexes where Element == LocationIndex {} -extension BitSet128: SetOfEntryIDs where Element == LeagueEntry.IDValue { +extension BitSet128: SetOfEntryIDs where Element == Entry.IDValue { func availableMatchupPairs( assignedEntryHomeAways: AssignedEntryHomeAways, - maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups - ) -> Set { - var pairs = Set(minimumCapacity: (count-1) * 2) + maxSameOpponentMatchups: MaximumSameOpponentMatchups + ) -> Set { + var pairs = Set(minimumCapacity: (count-1) * 2) var index = 0 forEach { home in let assignedHome = assignedEntryHomeAways[unchecked: home] diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index 4f9dfe4..3517bc3 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -116,16 +116,16 @@ extension BitSet64 { // MARK: AbstractSet extension BitSet64: AbstractSet {} -extension BitSet64: SetOfDayIndexes where Element == LeagueTimeIndex {} -extension BitSet64: SetOfTimeIndexes where Element == LeagueTimeIndex {} -extension BitSet64: SetOfLocationIndexes where Element == LeagueLocationIndex {} +extension BitSet64: SetOfDayIndexes where Element == TimeIndex {} +extension BitSet64: SetOfTimeIndexes where Element == TimeIndex {} +extension BitSet64: SetOfLocationIndexes where Element == LocationIndex {} -extension BitSet64: SetOfEntryIDs where Element == LeagueEntry.IDValue { +extension BitSet64: SetOfEntryIDs where Element == Entry.IDValue { func availableMatchupPairs( assignedEntryHomeAways: AssignedEntryHomeAways, - maxSameOpponentMatchups: LeagueMaximumSameOpponentMatchups - ) -> Set { - var pairs = Set(minimumCapacity: (count-1) * 2) + maxSameOpponentMatchups: MaximumSameOpponentMatchups + ) -> Set { + var pairs = Set(minimumCapacity: (count-1) * 2) var index = 0 forEach { home in let assignedHome = assignedEntryHomeAways[unchecked: home] diff --git a/Sources/league-scheduling/util/EntryAssignmentData.swift b/Sources/league-scheduling/util/EntryAssignmentData.swift index a7bba88..6a1920f 100644 --- a/Sources/league-scheduling/util/EntryAssignmentData.swift +++ b/Sources/league-scheduling/util/EntryAssignmentData.swift @@ -12,40 +12,40 @@ struct EntryAssignmentData: Sendable { var maximumPlayableMatchups:UInt32 /// Remaining allocations for the current `day`. - var remainingAllocations:Set + var remainingAllocations:Set /// When entries can play against each other again. /// - /// - Usage: [opponent `LeagueEntry.IDValue`: `LeagueRecurringDayLimitInterval`] - var recurringDayLimits:[LeagueRecurringDayLimitInterval] + /// - Usage: [opponent `Entry.IDValue`: `RecurringDayLimitInterval`] + var recurringDayLimits:[RecurringDayLimitInterval] var assignedTimes:ContiguousArray var assignedLocations:ContiguousArray /// Number of times an entry was assigned to play at home or away against another entry. /// - /// - Usage: [opponent `LeagueEntry.IDValue`: [`LeagueSchedule.HomeAwayValue`]] + /// - Usage: [opponent `Entry.IDValue`: [`LeagueSchedule.HomeAwayValue`]] var assignedEntryHomeAways:[HomeAwayValue] /// Maximum number of times an entry can play against another entry. /// - /// - Usage: [opponent `LeagueEntry.IDValue`: `maximum allowed matchups for opponent`] - var maxSameOpponentMatchups:ContiguousArray + /// - Usage: [opponent `Entry.IDValue`: `maximum allowed matchups for opponent`] + var maxSameOpponentMatchups:ContiguousArray - var playsAt:Set - var playsAtTimes:BitSet64 - var playsAtLocations:BitSet64 + var playsAt:Set + var playsAtTimes:BitSet64 + var playsAtLocations:BitSet64 - var maxTimeAllocations:[LeagueTimeIndex] - var maxLocationAllocations:[LeagueLocationIndex] + var maxTimeAllocations:[TimeIndex] + var maxLocationAllocations:[LocationIndex] } // MARK: Assign extension EntryAssignmentData { mutating func assignHome( - to slot: LeagueAvailableSlot, - away: LeagueEntry.IDValue, - recurringDayLimitInterval: LeagueRecurringDayLimitInterval + to slot: AvailableSlot, + away: Entry.IDValue, + recurringDayLimitInterval: RecurringDayLimitInterval ) { totalNumberOfHomeMatchupsPlayedSoFar += 1 assignedEntryHomeAways[unchecked: away].home += 1 @@ -56,9 +56,9 @@ extension EntryAssignmentData { } mutating func assignAway( - to slot: LeagueAvailableSlot, - home: LeagueEntry.IDValue, - recurringDayLimitInterval: LeagueRecurringDayLimitInterval + to slot: AvailableSlot, + home: Entry.IDValue, + recurringDayLimitInterval: RecurringDayLimitInterval ) { totalNumberOfAwayMatchupsPlayedSoFar += 1 assignedEntryHomeAways[unchecked: home].away += 1 @@ -69,7 +69,7 @@ extension EntryAssignmentData { } private mutating func assign( - to slot: LeagueAvailableSlot + to slot: AvailableSlot ) { totalNumberOfMatchupsPlayedSoFar += 1 assignedTimes[unchecked: slot.time] += 1 diff --git a/Sources/league-scheduling/util/ExecutionStep.swift b/Sources/league-scheduling/util/ExecutionStep.swift index b088bd5..1217eed 100644 --- a/Sources/league-scheduling/util/ExecutionStep.swift +++ b/Sources/league-scheduling/util/ExecutionStep.swift @@ -6,12 +6,12 @@ public struct ExecutionStep: Sendable { public let key:String public let nanoseconds:String - public init(key: String, duration: Duration) { + init(key: String, duration: Duration) { self.key = key let totalNano = UInt64(duration.components.seconds * 1_000_000_000) + UInt64(duration.components.attoseconds / 1_000_000_000) nanoseconds = ExecutionStep.numberFormatter.string(from: NSNumber(value: totalNano)) ?? "\(totalNano)" } - public init(key: String, nanoseconds: String) { + init(key: String, nanoseconds: String) { self.key = key self.nanoseconds = nanoseconds } diff --git a/Sources/league-scheduling/util/GameGap.swift b/Sources/league-scheduling/util/GameGap.swift index c2b3051..febe650 100644 --- a/Sources/league-scheduling/util/GameGap.swift +++ b/Sources/league-scheduling/util/GameGap.swift @@ -1,13 +1,13 @@ -public enum GameGap: Equatable, Sendable { +enum GameGap: Equatable, Sendable { case no case always(Int) case upTo(Int) case minimumOf(Int) - public typealias TupleValue = (min: Int, max: Int) + typealias TupleValue = (min: Int, max: Int) - public init?(htmlInputValue: some StringProtocol) { + init?(htmlInputValue: some StringProtocol) { let values = htmlInputValue.lowercased().split(separator: " ") switch values.first { case "no": @@ -26,7 +26,7 @@ public enum GameGap: Equatable, Sendable { } } - public var minMax: TupleValue { + var minMax: TupleValue { switch self { case .no: (1, 1) case .always(let v): (v+1, v+1) diff --git a/Sources/league-scheduling/util/LeagueError.swift b/Sources/league-scheduling/util/LeagueError.swift index f5db6d7..5cd29eb 100644 --- a/Sources/league-scheduling/util/LeagueError.swift +++ b/Sources/league-scheduling/util/LeagueError.swift @@ -4,10 +4,10 @@ public enum LeagueError: CustomStringConvertible, Error, Sendable { case malformedInput(msg: String? = nil) case failedNegativeDayIndex - case failedZeroExpectedMatchupsForDay(LeagueDayIndex) + case failedZeroExpectedMatchupsForDay(UInt32) case failedRedistributionRequiresPreviouslyScheduledMatchups - case failedRedistributingMatchupsForDay(LeagueDayIndex) - case failedAssignment(balanceTimeStrictness: LeagueBalanceStrictness) + case failedRedistributingMatchupsForDay(UInt32) + case failedAssignment(regenerationAttemptsThreshold: UInt32, balanceTimeStrictness: LitLeagues_Leagues_BalanceStrictness) case timedOut(function: String) @@ -31,8 +31,8 @@ public enum LeagueError: CustomStringConvertible, Error, Sendable { case .failedRedistributingMatchupsForDay(let dayIndex): return "Failed trying to redistribute matchups for dayIndex \(dayIndex); something is misconfigured" - case .failedAssignment(let balanceTimeStrictness): - var string = "Failed location/time assignment in \(Leagues3.failedRegenerationAttemptsThreshold) attempts; something may be misconfigured; try regenerating" + case .failedAssignment(let regenerationAttemptsThreshold, let balanceTimeStrictness): + var string = "Failed location/time assignment in \(regenerationAttemptsThreshold) attempts; something may be misconfigured; try regenerating" if balanceTimeStrictness != .relaxed { string += " or using 'relaxed' time strictness" } @@ -42,4 +42,4 @@ public enum LeagueError: CustomStringConvertible, Error, Sendable { return "Failed to build schedule within provided time limit; try regenerating (timed out; function=" + function + ")" } } -} +} \ No newline at end of file diff --git a/Sources/league-scheduling/util/LeagueShuffleAction.swift b/Sources/league-scheduling/util/LeagueShuffleAction.swift index 7c8a7e4..9c1bd3d 100644 --- a/Sources/league-scheduling/util/LeagueShuffleAction.swift +++ b/Sources/league-scheduling/util/LeagueShuffleAction.swift @@ -1,9 +1,9 @@ public struct LeagueShuffleAction: Sendable { - public let day:LeagueDayIndex - public let from:LeagueAvailableSlot - public let to:LeagueAvailableSlot - public let pair:LeagueMatchupPair + public let day:UInt32 + public let from:LitLeagues_Leagues_AvailableSlot + public let to:LitLeagues_Leagues_AvailableSlot + public let pair:LitLeagues_Leagues_MatchupPair } // MARK: Codable diff --git a/Tests/LeagueSchedulingTests/BalanceNumberCalculation.swift b/Tests/LeagueSchedulingTests/BalanceNumberCalculation.swift index 87c2a42..9eebc9a 100644 --- a/Tests/LeagueSchedulingTests/BalanceNumberCalculation.swift +++ b/Tests/LeagueSchedulingTests/BalanceNumberCalculation.swift @@ -3,8 +3,8 @@ import Testing struct BalanceNumberCalculation { - @Test(arguments: [LeagueBalanceStrictness.lenient, .relaxed, .normal, .very]) - func balanceNumberCalculation(strictness: LeagueBalanceStrictness) { + @Test(arguments: [BalanceStrictness.lenient, .relaxed, .normal, .very]) + func balanceNumberCalculation(strictness: BalanceStrictness) { // `totalMatchupsPlayed` = total number of matchups played by a single entry/team in a schedule // `value` = number of available times/locations let mutateMinimum:(_ value: inout Int) -> Void diff --git a/Tests/LeagueSchedulingTests/CanPlayAtTests.swift b/Tests/LeagueSchedulingTests/CanPlayAtTests.swift index 6520654..c56fc4c 100644 --- a/Tests/LeagueSchedulingTests/CanPlayAtTests.swift +++ b/Tests/LeagueSchedulingTests/CanPlayAtTests.swift @@ -12,14 +12,14 @@ struct CanPlayAtTests { var gameGap = GameGap.upTo(1).minMax var playsAt:PlaysAt.Element = [] - var playsAtTimes:BitSet64 = .init() - var timeNumbers:LeagueAssignedTimes.Element = .init(repeating: 0, count: times) - var locationNumbers:LeagueAssignedLocations.Element = .init(repeating: 0, count: locations) + var playsAtTimes:BitSet64 = .init() + var timeNumbers:AssignedTimes.Element = .init(repeating: 0, count: times) + var locationNumbers:AssignedLocations.Element = .init(repeating: 0, count: locations) let maxTimeNumbers:MaximumTimeAllocations.Element = .init(repeating: 1, count: times) var maxLocationNumbers:MaximumLocationAllocations.Element = .init(repeating: 1, count: locations) - var location:LeagueLocationIndex = 0 - for time in 0.. = [] + var time:TimeIndex = 0 + var location:LocationIndex = 0 + var playsAt:Set = [] var gameGap = GameGap.upTo(5).minMax #expect(CanPlayAtWithTravelDurations.test( @@ -112,7 +112,7 @@ extension CanPlayAtTests { )) matchupDuration = .minutes(30) - playsAt = [LeagueAvailableSlot(time: 1, location: 0)] + playsAt = [AvailableSlot(time: 1, location: 0)] #expect(CanPlayAtWithTravelDurations.test( startingTimes: startingTimes, matchupDuration: matchupDuration, diff --git a/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift b/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift index d1af8c5..cbd71c9 100644 --- a/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift +++ b/Tests/LeagueSchedulingTests/LeagueHTMLFormTests.swift @@ -68,7 +68,7 @@ extension LeagueHTMLFormTests { @Test func leagueHTMLFormMatchupsPerGameDay() throws { var payload = payload() - let matchupsPerGameDay:[LeagueEntryMatchupsPerGameDay] = [2, 4, 2, 2, 3, 2, 2, 5] + let matchupsPerGameDay:[EntryMatchupsPerGameDay] = [2, 4, 2, 2, 3, 2, 2, 5] for i in 0.. LeagueRequestPayload { - let gameDays:LeagueDayIndex = 8 + func payload() -> RequestPayload { + let gameDays:DayIndex = 8 let teamsCount = 9 - var teams = [LeagueEntry]() - var gameTimes = LeagueGameTimes() + var teams = [Entry]() + var gameTimes = GameTimes() gameTimes.times = [0, 1, 2] - var gameLocations = LeagueGameLocations() + var gameLocations = GameLocations() gameLocations.locations = [0, 1, 2] for _ in 0.. = calculateAdjacentTimes(for: 0, entryMatchupsPerGameDay: 2) + var adjacent:BitSet64 = calculateAdjacentTimes(for: 0, entryMatchupsPerGameDay: 2) #expect(adjacent == .init([1])) adjacent = calculateAdjacentTimes(for: 0, entryMatchupsPerGameDay: 3) diff --git a/Tests/LeagueSchedulingTests/ProtobufTests.swift b/Tests/LeagueSchedulingTests/ProtobufTests.swift index 589c313..cdef3c9 100644 --- a/Tests/LeagueSchedulingTests/ProtobufTests.swift +++ b/Tests/LeagueSchedulingTests/ProtobufTests.swift @@ -15,7 +15,7 @@ struct ProtobufTests { @Test func protobufMatchupPair() throws { - var pair = LeagueMatchupPair(team1: 10, team2: 20) + var pair = MatchupPair(team1: 10, team2: 20) var binary:[UInt8] = try pair.serializedBytes() var json = try pair.jsonString(options: Self.options) #expect(binary == [8, 10, 16, 20]) @@ -26,7 +26,7 @@ struct ProtobufTests { binary = try pair.serializedBytes() json = try pair.jsonString(options: Self.options) #expect(binary == [8, 2, 16, 255, 255, 255, 255, 15]) - #expect(json == #"{"team1":2,"team2":\#(LeagueEntry.IDValue.max)}"#) + #expect(json == #"{"team1":2,"team2":\#(Entry.IDValue.max)}"#) } @Test diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift index a50bac3..038ea3b 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBack2Back.swift @@ -9,11 +9,11 @@ struct ScheduleBack2Back: ScheduleTestsProtocol { // MARK: 2 divisions | 12/12 @Test(.timeLimit(.minutes(1))) func scheduleB2B_11GameDays4Times6Locations2Divisions24TeamsEvenSplit() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 6, 24) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 12)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 12)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 4, 6, 24) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 12)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 12)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -58,11 +58,11 @@ extension ScheduleBack2Back { try expectations(settings: schedule, matchupsCount: 264, data: data) } static func scheduleB2B_11GameDays4Times6Locations2Divisions24Teams14_10() throws -> UnitTestRuntimeSchedule { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 6, 24) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 14)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 10)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 4, 6, 24) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 14)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 10)) return Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -100,11 +100,11 @@ extension ScheduleBack2Back { // MARK: 2 divisions | 11/10 @Test(.timeLimit(.minutes(1))) func scheduleB2B_10GameDays4Times6Locations2Divisions21Teams11_10() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 5, 21) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 11)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 10)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 5, 21) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 11)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 10)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift index 86aa789..913553e 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBaseball.swift @@ -12,8 +12,8 @@ extension ScheduleBaseball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=Mjc0OTMzNg==&arg3=MzU2NzQ= @Test(.timeLimit(.minutes(1))) func scheduleBaseball_7GameDays2Times2Locations1Division8Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (7, 2, 2, 8) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (7, 2, 2, 8) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -50,8 +50,8 @@ extension ScheduleBaseball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MjMwMDc5OA==&arg3=MzA2OTA= @Test(.timeLimit(.minutes(1))) func scheduleBaseball_8GameDays2Times2Locations1Division8Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 2, 2, 8) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (8, 2, 2, 8) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift index 9b87c47..1a18503 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleBeanBagToss.swift @@ -17,10 +17,10 @@ struct ScheduleBeanBagToss: ScheduleTestsProtocol { ) } static func schedule8GameDays3Times3Locations1Division9Teams() throws -> UnitTestRuntimeSchedule { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 3, 3, 9) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (8, 3, 3, 9) let entries = getEntries( - divisions: [LeagueDivision.IDValue](repeating: 0, count: teams), + divisions: [Division.IDValue](repeating: 0, count: teams), gameDays: gameDays, times: times, locations: locations, @@ -53,11 +53,11 @@ extension ScheduleBeanBagToss { // MARK: 11GD | 4T | 6L | 2D | 12x12T @Test(.timeLimit(.minutes(1))) func scheduleB2B_11GameDays4Times6Locations2Divisions24Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 6, 24) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 12)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 12)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 4, 6, 24) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 12)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 12)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -96,8 +96,8 @@ extension ScheduleBeanBagToss { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MjUwNTE2OA==&arg3=MzM0NjM= @Test(.timeLimit(.minutes(1))) func schedule5GameDays3Times6Locations1Division13Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (5, 3, 6, 13) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (5, 3, 6, 13) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -134,11 +134,11 @@ extension ScheduleBeanBagToss { // https://secure.rec1.com/MN/owatonna-mn/leagues/publicLeague/2505159 @Test(.timeLimit(.minutes(1))) func schedule5GameDays4Times8Locations2Divisions25Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (5, 4, 8, 25) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 9)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 16)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (5, 4, 8, 25) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 9)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 16)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -176,11 +176,11 @@ extension ScheduleBeanBagToss { // MARK: 10GD | 3T | 6L | 2D | 12x12T @Test(.timeLimit(.minutes(1))) func schedule10GameDays3Times8Locations2Divisions24Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 3, 8, 24) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 12)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 12)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 3, 8, 24) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 12)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 12)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -217,11 +217,11 @@ extension ScheduleBeanBagToss { // MARK: 11GD | 4T | 6L | 2D | 12x11T @Test(.timeLimit(.minutes(1))) func schedule11GameDays4Times6Locations2Divisions23Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 6, 23) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 12)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 11)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 4, 6, 23) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 12)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 11)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -259,11 +259,11 @@ extension ScheduleBeanBagToss { // MARK: 10GD | 4T | 6L | 2D | 11x12T @Test(.timeLimit(.minutes(1))) func schedule10GameDays4Times6Locations2Divisions23Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 6, 23) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 11)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 12)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 6, 23) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 11)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 12)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -301,12 +301,12 @@ extension ScheduleBeanBagToss { // MARK: 10GD | 4T | 8L | 3D | 8x8x10T @Test(.timeLimit(.minutes(1))) func scheduleB2B_10GameDays4Times8Locations3Divisions26Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 8, 26) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 8)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 8)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 2, count: 10)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 8, 26) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 8)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 8)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 2, count: 10)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -345,11 +345,11 @@ extension ScheduleBeanBagToss { // MARK: 11GD | 4T | 8L | 2D | 12x12T //@Test(.timeLimit(.minutes(1))) // TODO: support func scheduleB2B_11GameDays4Times8Locations2DivisionsDifferentTimes24Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 8, 24) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 12)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 12)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 4, 8, 24) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 12)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 12)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -389,8 +389,8 @@ extension ScheduleBeanBagToss { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=Mjc0OTM3Ng==&arg3=Mzg4NjQ= @Test(.timeLimit(.minutes(1))) func scheduleBeanBagToss_10GameDays4Time8Locations1Division21Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 8, 21) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 8, 21) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -429,8 +429,8 @@ extension ScheduleBeanBagToss { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MjMwMTU4Nw==&arg3=MzEwMjQ= @Test(.timeLimit(.minutes(1))) func scheduleBeanBagToss_10GameDays4Times6Locations2Division23Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 6, 23) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 6, 23) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift index 260ba30..39d724e 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleFootball.swift @@ -12,8 +12,8 @@ extension ScheduleFootball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MjUwNTQwNw==&arg3=MzI3Nzg= @Test(.timeLimit(.minutes(1))) func scheduleFootball_10GameDays1Time3Locations1Division6Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 1, 3, 6) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 1, 3, 6) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift index 798ac23..0cefb56 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleMisc.swift @@ -8,8 +8,8 @@ struct ScheduleMisc: ScheduleTestsProtocol { // MARK: 5GD | 4T | 3L | 1D | 8T | 1M @Test(.timeLimit(.minutes(1))) func schedule5GameDays4Times3Locations1Division8Teams1Matchup() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (5, 4, 3, 8) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (5, 4, 3, 8) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -52,11 +52,11 @@ extension ScheduleMisc { // MARK: 5GD | 4T | 3L | 1D | 8T | 1M @Test(.timeLimit(.minutes(1))) func schedule5GameDays4Times3Locations2Divisions16Teams1Matchup() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (5, 4, 3, 16) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 8)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 8)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (5, 4, 3, 16) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 8)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 8)) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -100,8 +100,8 @@ extension ScheduleMisc { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MjUwOTgxMA==&arg3=MzQ1OTg= @Test(.timeLimit(.minutes(1))) func schedule6GameDays2Times2Locations1Division6Teams1Matchup() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (6, 2, 2, 6) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (6, 2, 2, 6) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -141,8 +141,8 @@ extension ScheduleMisc { // MARK: 8GD | 8T | 3L | 1D | 9T | 4M @Test(.timeLimit(.minutes(1))) func schedule8GameDays8Times3Locations1Division9Teams4Matchups() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 4 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 8, 3, 9) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 4 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (8, 8, 3, 9) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -199,11 +199,11 @@ extension ScheduleMisc { } static func schedule10GameDays4Times5Locations2Divisions20Teams2Matchups() throws -> UnitTestRuntimeSchedule { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 5, 20) - var entryDivisions = [LeagueDivision.IDValue]() - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 0, count: 10)) - entryDivisions.append(contentsOf: [LeagueDivision.IDValue](repeating: 1, count: 10)) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 5, 20) + var entryDivisions = [Division.IDValue]() + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 0, count: 10)) + entryDivisions.append(contentsOf: [Division.IDValue](repeating: 1, count: 10)) return getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift index 3dff988..50cdfd4 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleRedistribution.swift @@ -12,8 +12,8 @@ extension ScheduleRedistribution { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MzU3MzM1MA==&arg3=NTA5Mzk= @Test func scheduleRedistribution_11GameDays3Times1Location5Teams12MaxMatchupsPerEntry() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 3, 1, 5) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 3, 1, 5) let entries = Self.getEntries( divisions: .init(repeating: 0, count: teams), gameDays: gameDays, @@ -57,8 +57,8 @@ extension ScheduleRedistribution { // MARK: 11GD | 3T | 1L | 1D | 7T @Test func scheduleRedistribution_11GameDays3Times1Location7Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 3, 1, 7) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 3, 1, 7) let entries = Self.getEntries( divisions: .init(repeating: 0, count: teams), gameDays: gameDays, @@ -103,8 +103,8 @@ extension ScheduleRedistribution { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=Mjk3NjYzNA==&arg3=NDEwMjA= @Test(.timeLimit(.minutes(1))) func scheduleRedistribution_6GameDays4Times4Locations1Division15Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (6, 4, 4, 15) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (6, 4, 4, 15) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -148,8 +148,8 @@ extension ScheduleRedistribution { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MjUwNTQwNg%3D%3D&arg3=MzI2MjA%3D @Test(.timeLimit(.minutes(1))) func scheduleRedistribution_10GameDays4Times4Locations1Division9Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 4, 9) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 4, 9) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift index 7cdedaa..e4d0ab4 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleSameLocationIfB2B.swift @@ -17,10 +17,10 @@ struct ScheduleSameLocationIfB2B: ScheduleTestsProtocol { ) } static func scheduleSameLocationIfB2B_8GameDays3Times3Locations1Division9Teams() throws -> UnitTestRuntimeSchedule { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (8, 3, 3, 9) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (8, 3, 3, 9) let entries = getEntries( - divisions: [LeagueDivision.IDValue](repeating: 0, count: teams), + divisions: [Division.IDValue](repeating: 0, count: teams), gameDays: gameDays, times: times, locations: locations, @@ -62,10 +62,10 @@ extension ScheduleSameLocationIfB2B { ) } static func scheduleSameLocationIfB2B_12GameDays3Times1Locations1Division5Teams() throws -> UnitTestRuntimeSchedule { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 3, 1, 5) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (12, 3, 1, 5) let entries = getEntries( - divisions: [LeagueDivision.IDValue](repeating: 0, count: teams), + divisions: [Division.IDValue](repeating: 0, count: teams), gameDays: gameDays, times: times, locations: locations, @@ -108,10 +108,10 @@ extension ScheduleSameLocationIfB2B { ) } static func scheduleSameLocationIfB2B_10GameDays4Times4Locations1Division16Teams() throws -> UnitTestRuntimeSchedule { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 4, 16) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 4, 16) let entries = getEntries( - divisions: [LeagueDivision.IDValue](repeating: 0, count: teams), + divisions: [Division.IDValue](repeating: 0, count: teams), gameDays: gameDays, times: times, locations: locations, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift index 0a420c1..51f3f00 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleSoftball.swift @@ -14,8 +14,8 @@ extension ScheduleSoftball { // https://secure.rec1.com/MN/owatonna-mn/leagues/publicLeague/2301542 @Test(.timeLimit(.minutes(1))) func scheduleB2B_11GameDays4Times3Locations1Division12Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (11, 4, 3, 12) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (11, 4, 3, 12) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -54,8 +54,8 @@ extension ScheduleSoftball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MzMwMjc3Mw==&arg3=NDY4Mjg= @Test(.timeLimit(.minutes(1))) func scheduleSoftball_10GameDays4Times4Locations1Division16Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 4, 4, 16) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 4, 4, 16) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, @@ -94,8 +94,8 @@ extension ScheduleSoftball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=Mjc0OTM0OQ==&arg3=Mzg1NDc= @Test(.timeLimit(.minutes(1))) func scheduleSoftball_10GameDays3Times3Locations1Division9Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (10, 3, 3, 9) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (10, 3, 3, 9) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift index 7d8fdef..7a01b77 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleTball.swift @@ -12,8 +12,8 @@ extension ScheduleTball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=Mjc0OTI3NQ==&arg3=Mzg5OTk= @Test(.timeLimit(.minutes(1))) func scheduleTball_4GameDays3Times1Location1Division6Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (4, 3, 1, 6) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (4, 3, 1, 6) let schedule = Self.getSchedule( gameDays: gameDays, entryMatchupsPerGameDay: maxEntryMatchupsPerGameDay, diff --git a/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift b/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift index f2f8517..46a0c0c 100644 --- a/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift +++ b/Tests/LeagueSchedulingTests/schedules/ScheduleVolleyball.swift @@ -8,8 +8,8 @@ import Testing struct ScheduleVolleyball: ScheduleTestsProtocol { @Test(.timeLimit(.minutes(1))) func scheduleVOLLEYBALL() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 4, 1, 7) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (12, 4, 1, 7) let entries = Self.getEntries( divisions: .init(repeating: 0, count: teams), gameDays: gameDays, @@ -50,8 +50,8 @@ extension ScheduleVolleyball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MzU3MzM1MA==&arg3=NTA5Mzk= @Test func scheduleVolleyball_12GameDays3Times1Location5Teams1Matchup() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 1 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 3, 1, 5) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 1 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (12, 3, 1, 5) let entries = Self.getEntries( divisions: .init(repeating: 0, count: teams), gameDays: gameDays, @@ -91,8 +91,8 @@ extension ScheduleVolleyball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MzU3MzM1MA==&arg3=NTA5Mzk= @Test func scheduleVolleyball_12GameDays3Times1Location5Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 3, 1, 5) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (12, 3, 1, 5) let entries = Self.getEntries( divisions: .init(repeating: 0, count: teams), gameDays: gameDays, @@ -132,8 +132,8 @@ extension ScheduleVolleyball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=MzU3MzM1MA==&arg3=NTA5Mzk= @Test func scheduleVolleyball_12GameDays4Times1Location5Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 4, 1, 5) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (12, 4, 1, 5) let entries = Self.getEntries( divisions: .init(repeating: 0, count: teams), gameDays: gameDays, @@ -174,8 +174,8 @@ extension ScheduleVolleyball { // https://secure.rec1.com/MN/owatonna-mn/leagueschedule.php?arg1=Mjk5NTUyNw==&arg3=NDI0OTE= @Test func scheduleVolleyball_12GameDays3Times2Location11Teams() async throws { - let maxEntryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay = 2 - let (gameDays, times, locations, teams):(LeagueDayIndex, LeagueTimeIndex, LeagueLocationIndex, Int) = (12, 3, 2, 11) + let maxEntryMatchupsPerGameDay:EntryMatchupsPerGameDay = 2 + let (gameDays, times, locations, teams):(DayIndex, TimeIndex, LocationIndex, Int) = (12, 3, 2, 11) let entries = Self.getEntries( divisions: .init(repeating: 0, count: teams), gameDays: gameDays, diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift index ee627bb..776b233 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/BalanceHomeAwayExpectations.swift @@ -4,10 +4,10 @@ import Testing struct BalanceHomeAwayExpectations: ScheduleTestsProtocol { func expectations( - cap: LeagueMaximumSameOpponentMatchupsCap, + cap: MaximumSameOpponentMatchupsCap, matchupsPlayedPerDay: ContiguousArray>, assignedEntryHomeAways: AssignedEntryHomeAways, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, divisionEntries: [Config.EntryRuntime] ) { for entry in divisionEntries { @@ -43,7 +43,7 @@ struct BalanceHomeAwayExpectations: ScheduleTests entry: Config.EntryRuntime, matchupsPlayedPerDay: ContiguousArray>, assignedEntryHomeAways: AssignedEntryHomeAways, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay + entryMatchupsPerGameDay: EntryMatchupsPerGameDay ) -> Bool { let entryMatchupsPlayed = matchupsPlayedPerDay.reduce(0, { $0 + $1[unchecked: entry.id] }) let halfMatchupsPlayed = entryMatchupsPlayed / entryMatchupsPerGameDay @@ -61,7 +61,7 @@ struct BalanceHomeAwayExpectations: ScheduleTests } extension BalanceHomeAwayExpectations { private static func totalHomeAwayGamesPlayed( - for team: LeagueEntry.IDValue, + for team: Entry.IDValue, assignedEntryHomeAways: AssignedEntryHomeAways ) -> (home: Int, away: Int) { var home = 0 @@ -74,7 +74,7 @@ extension BalanceHomeAwayExpectations { } } -extension LeagueEntry.Runtime { +extension Entry.Runtime { func isEqual(to rhs: Self) -> Bool { id == rhs.id && division == rhs.division diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift index d111e7c..85cecf5 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/DayExpectations.swift @@ -5,7 +5,7 @@ import Testing struct DayExpectations: ScheduleTestsProtocol { let b2bMatchupsAtDifferentLocations:Set - func expectations(_ settings: LeagueGeneralSettings.Runtime) { + func expectations(_ settings: GeneralSettings.Runtime) { if settings.sameLocationIfB2B { sameLocationIfB2B() } diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift index bf3d7b8..03b3267 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/DivisionEntryExpectations.swift @@ -3,10 +3,10 @@ import Testing struct DivisionEntryExpectations: ScheduleTestsProtocol { - let cap:LeagueMaximumSameOpponentMatchupsCap + let cap:MaximumSameOpponentMatchupsCap let matchupsPlayedPerDay:ContiguousArray> let assignedEntryHomeAways:AssignedEntryHomeAways - let entryMatchupsPerGameDay:LeagueEntryMatchupsPerGameDay + let entryMatchupsPerGameDay:EntryMatchupsPerGameDay func expectations( balanceHomeAway: Bool, diff --git a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift index 5667fed..4d8310c 100644 --- a/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift +++ b/Tests/LeagueSchedulingTests/schedules/expectations/ScheduleExpectations.swift @@ -8,7 +8,7 @@ protocol ScheduleExpectations: Sendable { // MARK: Expectations extension ScheduleExpectations { func expectations( - settings: borrowing LeagueRequestPayload.Runtime, + settings: borrowing RequestPayload.Runtime, matchupsCount: Int, data: LeagueGenerationResult ) throws { @@ -29,11 +29,11 @@ extension ScheduleExpectations { let gameDays = settings.gameDays let entryMatchupsPerGameDay = settings.general.defaultMaxEntryMatchupsPerGameDay let entriesPerLocation = settings.general.entriesPerLocation - var maxStartingTimes:LeagueTimeIndex = 0 - var maxLocations:LeagueLocationIndex = 0 + var maxStartingTimes:TimeIndex = 0 + var maxLocations:LocationIndex = 0 for setting in settings.daySettings { if setting.startingTimes.count > maxStartingTimes { - maxStartingTimes = LeagueTimeIndex(setting.startingTimes.count) + maxStartingTimes = TimeIndex(setting.startingTimes.count) } if setting.locations > maxLocations { maxLocations = setting.locations @@ -44,8 +44,8 @@ extension ScheduleExpectations { for result in data.results { let matchups = result.schedule var numberOfAssignedMatchups = [Int](repeating: 0, count: entriesCount) - var assignedTimes = LeagueAssignedTimes(repeating: .init(repeating: UInt8(0), count: maxStartingTimes), count: entriesCount) - var assignedLocations = LeagueAssignedLocations(repeating: .init(repeating: UInt8(0), count: maxLocations), count: entriesCount) + var assignedTimes = AssignedTimes(repeating: .init(repeating: UInt8(0), count: maxStartingTimes), count: entriesCount) + var assignedLocations = AssignedLocations(repeating: .init(repeating: UInt8(0), count: maxLocations), count: entriesCount) var matchupsPerDay = ContiguousArray(repeating: UInt32(0), count: gameDays) var matchupsPlayedPerDay = ContiguousArray(repeating: ContiguousArray(repeating: 0, count: entriesCount), count: gameDays) var assignedEntryHomeAways = AssignedEntryHomeAways(repeating: .init(repeating: .init(home: 0, away: 0), count: entriesCount), count: entriesCount) @@ -55,7 +55,7 @@ extension ScheduleExpectations { matchupsPerDay[unchecked: dayIndex] = UInt32(matchups.count) var b2bMatchupsAtDifferentLocations = Set() - var assignedSlots = [Set](repeating: [], count: entriesCount) + var assignedSlots = [Set](repeating: [], count: entriesCount) for matchup in matchups { let home = matchup.home let away = matchup.away @@ -75,7 +75,7 @@ extension ScheduleExpectations { let homeSlots = assignedSlots[unchecked: home] if homeSlots.count > 1 { insertB2BSlotsAtDifferentLocations( - dayIndex: LeagueDayIndex(dayIndex), + dayIndex: DayIndex(dayIndex), matchup: matchup, slots: homeSlots, b2bMatchupsAtDifferentLocations: &b2bMatchupsAtDifferentLocations @@ -84,7 +84,7 @@ extension ScheduleExpectations { let awaySlots = assignedSlots[unchecked: away] if awaySlots.count > 1 { insertB2BSlotsAtDifferentLocations( - dayIndex: LeagueDayIndex(dayIndex), + dayIndex: DayIndex(dayIndex), matchup: matchup, slots: awaySlots, b2bMatchupsAtDifferentLocations: &b2bMatchupsAtDifferentLocations @@ -120,7 +120,7 @@ extension ScheduleExpectations { #expect(assignedTimes == result.assignedTimes) #endif - let balanceTimeNumber:LeagueTimeIndex + let balanceTimeNumber:TimeIndex if !settings.general.balancedTimes.isEmpty { balanceTimeNumber = LeagueSchedule.balanceNumber( totalMatchupsPlayed: totalMatchupsPlayed, @@ -140,7 +140,7 @@ extension ScheduleExpectations { #expect(assignedLocations == result.assignedLocations) #endif - let balanceLocationNumber:LeagueLocationIndex + let balanceLocationNumber:LocationIndex if !settings.general.balancedLocations.isEmpty { balanceLocationNumber = LeagueSchedule.balanceNumber( totalMatchupsPlayed: totalMatchupsPlayed, @@ -190,20 +190,20 @@ extension ScheduleExpectations { // MARK: B2B slots at different locations extension ScheduleExpectations { func insertB2BSlotsAtDifferentLocations( - dayIndex: LeagueDayIndex, - matchup: LeagueMatchup, - slots: Set, + dayIndex: DayIndex, + matchup: Matchup, + slots: Set, b2bMatchupsAtDifferentLocations: inout Set ) { for slot in slots { - var b2bTimes = Set() + var b2bTimes = Set() if slot.time > 0 { b2bTimes.insert(slot.time-1) } b2bTimes.insert(slot.time+1) let b2bSlotsAtDifferentLocation = slots.filter({ b2bTimes.contains($0.time) && $0.location != slot.location }) if !b2bSlotsAtDifferentLocation.isEmpty { - b2bMatchupsAtDifferentLocations.insert(.init(day: LeagueDayIndex(dayIndex), matchup: matchup)) + b2bMatchupsAtDifferentLocations.insert(.init(day: DayIndex(dayIndex), matchup: matchup)) } } } @@ -212,13 +212,13 @@ extension ScheduleExpectations { // MARK: Assigned times extension ScheduleExpectations { private func allocatedLessThanOrEqualToBalanceTimeNumber( - assignedTimes: LeagueAssignedTimes, + assignedTimes: AssignedTimes, balancedTimes: borrowing some SetOfTimeIndexes & ~Copyable, - balanceTimeNumber: LeagueTimeIndex + balanceTimeNumber: TimeIndex ) { for (entryID, assignedTimes) in assignedTimes.enumerated() { for (timeIndex, assignedTime) in assignedTimes.enumerated() { - guard balancedTimes.contains(LeagueTimeIndex(timeIndex)) else { continue } + guard balancedTimes.contains(TimeIndex(timeIndex)) else { continue } #expect(assignedTime <= balanceTimeNumber, Comment("entryID=\(entryID);timeIndex=\(timeIndex);assignedTime=\(assignedTime);balanceTimeNumber=\(balanceTimeNumber)")) } } @@ -228,13 +228,13 @@ extension ScheduleExpectations { // MARK: Assigned locations extension ScheduleExpectations { private func allocatedLessThanOrEqualToBalanceLocationNumber( - assignedLocations: LeagueAssignedLocations, + assignedLocations: AssignedLocations, balancedLocations: borrowing some SetOfLocationIndexes & ~Copyable, - balanceLocationNumber: LeagueLocationIndex + balanceLocationNumber: LocationIndex ) { for (entryID, assignedLocations) in assignedLocations.enumerated() { for (locationIndex, assignedLocation) in assignedLocations.enumerated() { - guard balancedLocations.contains(LeagueLocationIndex(locationIndex)) else { continue } + guard balancedLocations.contains(LocationIndex(locationIndex)) else { continue } #expect(assignedLocation <= balanceLocationNumber, Comment("entryID=\(entryID);locationIndex=\(locationIndex);assignedLocation=\(assignedLocation);balanceLocationNumber=\(balanceLocationNumber)")) } } @@ -245,7 +245,7 @@ extension ScheduleExpectations { extension ScheduleExpectations { func printMatchups( day: Int, - _ matchups: Set + _ matchups: Set ) { return let results:String = matchups.sorted(by: { diff --git a/Tests/LeagueSchedulingTests/schedules/util/MatchupsPlayedPerGameDay.swift b/Tests/LeagueSchedulingTests/schedules/util/MatchupsPlayedPerGameDay.swift index 0cb0e61..5a1ffa0 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/MatchupsPlayedPerGameDay.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/MatchupsPlayedPerGameDay.swift @@ -3,9 +3,9 @@ struct MatchupsPlayedPerGameDay { static func get( - gameDays: LeagueDayIndex, + gameDays: DayIndex, entriesCount: Int, - schedule: ContiguousArray> + schedule: ContiguousArray> ) -> ContiguousArray> { var matchupsPlayedPerDay = ContiguousArray( repeating: ContiguousArray(repeating: 0, count: entriesCount), diff --git a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift index dec913b..aded3a0 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift @@ -4,17 +4,17 @@ import struct FoundationEssentials.Date import StaticDateTimes protocol ScheduleTestsProtocol: ScheduleExpectations { - typealias UnitTestScheduleConfig = ScheduleConfig, BitSet64, BitSet64, BitSet64> - typealias UnitTestRuntimeSchedule = LeagueRequestPayload.Runtime + typealias UnitTestScheduleConfig = ScheduleConfig, BitSet64, BitSet64, BitSet64> + typealias UnitTestRuntimeSchedule = RequestPayload.Runtime } // MARK: Get entries extension ScheduleTestsProtocol { static func getEntries( - divisions: [LeagueDivision.IDValue], - gameDays: LeagueDayIndex, - times: LeagueTimeIndex, - locations: LeagueLocationIndex, + divisions: [Division.IDValue], + gameDays: DayIndex, + times: TimeIndex, + locations: LocationIndex, teams: Int, homeLocations: ContiguousArray = [], byes: ContiguousArray = [] @@ -25,8 +25,8 @@ extension ScheduleTestsProtocol { var entries = [UnitTestScheduleConfig.EntryRuntime]() entries.reserveCapacity(teams) for division in divisions { - let entry = LeagueEntry.Runtime( - id: LeagueEntry.IDValue(entries.count), + let entry = Entry.Runtime( + id: Entry.IDValue(entries.count), division: division, gameDays: playsOn[entries.count], gameTimes: playsAtTimes[entries.count], @@ -43,15 +43,15 @@ extension ScheduleTestsProtocol { extension ScheduleTestsProtocol { static func getDivision( - dayOfWeek: LeagueDayOfWeek, + dayOfWeek: DayOfWeek, gameGaps: [GameGap] = [], values: ( - gameDays: LeagueDayIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, + gameDays: DayIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, entriesCount: Int ) ) throws(LeagueError) -> UnitTestScheduleConfig.DivisionRuntime { - let maxSameOpponentMatchups = try LeagueRequestPayload.calculateMaximumSameOpponentMatchupsCap( + let maxSameOpponentMatchups = try RequestPayload.calculateMaximumSameOpponentMatchupsCap( gameDays: values.gameDays, entryMatchupsPerGameDay: values.entryMatchupsPerGameDay, entriesCount: values.entriesCount @@ -68,39 +68,39 @@ extension ScheduleTestsProtocol { // MARK: Get schedule extension ScheduleTestsProtocol { static func getSchedule( - gameDays: LeagueDayIndex, - entryMatchupsPerGameDay: LeagueEntryMatchupsPerGameDay, + gameDays: DayIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, maximumPlayableMatchups: [UInt32] = [], - entriesPerLocation: LeagueEntriesPerMatchup, - matchupDuration: LeagueMatchupDuration = 0, + entriesPerLocation: EntriesPerMatchup, + matchupDuration: MatchupDuration = 0, startingTimes: [StaticTime], - locations: LeagueLocationIndex, + locations: LocationIndex, optimizeTimes: Bool, prioritizeEarlierTimes: Bool = true, prioritizeHomeAway: Bool, balanceHomeAway: Bool = true, sameLocationIfB2B: Bool = false, redistributionSettings: LitLeagues_Leagues_RedistributionSettings? = nil, - balanceTimeStrictness: LeagueBalanceStrictness, - balanceLocationStrictness: LeagueBalanceStrictness, + balanceTimeStrictness: BalanceStrictness, + balanceLocationStrictness: BalanceStrictness, gameGaps: GameGap, divisions: [UnitTestScheduleConfig.DivisionRuntime], divisionsCanPlayOnSameDay: Bool = true, divisionsCanPlayAtSameTime: Bool = true, entries: [UnitTestScheduleConfig.EntryRuntime] - ) -> LeagueRequestPayload.Runtime { - let correctMaximumPlayableMatchups = LeagueRequestPayload.calculateMaximumPlayableMatchups( + ) -> RequestPayload.Runtime { + let correctMaximumPlayableMatchups = RequestPayload.calculateMaximumPlayableMatchups( gameDays: gameDays, entryMatchupsPerGameDay: entryMatchupsPerGameDay, teamsCount: entries.count, maximumPlayableMatchups: maximumPlayableMatchups ) - let times:LeagueTimeIndex = LeagueTimeIndex(startingTimes.count) - let timeSlots:BitSet64 = .init(0.. = .init(0...init( + let times:TimeIndex = TimeIndex(startingTimes.count) + let timeSlots:BitSet64 = .init(0.. = .init(0...init( gameGap: gameGaps, - timeSlots: LeagueTimeIndex(startingTimes.count), + timeSlots: TimeIndex(startingTimes.count), startingTimes: startingTimes, entriesPerLocation: entriesPerLocation, locations: locations, @@ -114,7 +114,7 @@ extension ScheduleTestsProtocol { balanceLocationStrictness: balanceLocationStrictness, balancedLocations: matchupSlots, redistributionSettings: redistributionSettings, - flags: LeagueSettingFlags.get( + flags: SettingFlags.get( optimizeTimes: optimizeTimes, prioritizeEarlierTimes: prioritizeEarlierTimes, prioritizeHomeAway: prioritizeHomeAway, @@ -123,14 +123,14 @@ extension ScheduleTestsProtocol { ) ) - var daySettings = [LeagueGeneralSettings.Runtime]() + var daySettings = [GeneralSettings.Runtime]() daySettings.reserveCapacity(gameDays) for day in 0.. Date: Mon, 16 Mar 2026 07:12:33 -0500 Subject: [PATCH 17/33] resolving merge conflicts --- Package.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Package.swift b/Package.swift index a272660..2623594 100644 --- a/Package.swift +++ b/Package.swift @@ -34,9 +34,7 @@ let package = Package( .product(name: "SwiftProtobuf", package: "swift-protobuf") ], path: "Sources/league-scheduling", - swiftSettings: [ - .enableUpcomingFeature("ExistentialAny") - ] + swiftSettings: [.enableUpcomingFeature("ExistentialAny")] ), .testTarget( name: "LeagueSchedulingTests", From 6ae650c826fe0f210e778ec9c739528a80d1dc37 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 07:13:55 -0500 Subject: [PATCH 18/33] resolving merge conflicts --- Package.swift | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Package.swift b/Package.swift index 2623594..5043a50 100644 --- a/Package.swift +++ b/Package.swift @@ -13,12 +13,10 @@ let package = Package( traits: [ .default(enabledTraits: [ "ProtobufCodable", - "UncheckedArraySubscript", - "SpecializeScheduleConfiguration" + "UncheckedArraySubscript" ]), .trait(name: "ProtobufCodable"), - .trait(name: "UncheckedArraySubscript"), - .trait(name: "SpecializeScheduleConfiguration") + .trait(name: "UncheckedArraySubscript") ], dependencies: [ .package(url: "https://github.com/RandomHashTags/swift-staticdatetime", from: "0.3.5"), @@ -41,4 +39,4 @@ let package = Package( dependencies: ["LeagueScheduling"] ) ] -) +) \ No newline at end of file From bc69ff8453d93610f13e6050879bfca9bb884cc2 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 07:15:34 -0500 Subject: [PATCH 19/33] resolving merge conflicts --- Package.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Package.swift b/Package.swift index 5043a50..84c455e 100644 --- a/Package.swift +++ b/Package.swift @@ -13,10 +13,12 @@ let package = Package( traits: [ .default(enabledTraits: [ "ProtobufCodable", - "UncheckedArraySubscript" + "UncheckedArraySubscript", + "SpecializeScheduleConfiguration" ]), .trait(name: "ProtobufCodable"), - .trait(name: "UncheckedArraySubscript") + .trait(name: "UncheckedArraySubscript"), + .trait(name: "SpecializeScheduleConfiguration") ], dependencies: [ .package(url: "https://github.com/RandomHashTags/swift-staticdatetime", from: "0.3.5"), From a06881b121bef8b1630b784263b453db0d821f6b Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 09:54:38 -0500 Subject: [PATCH 20/33] resolve merge conflicts --- Sources/league-scheduling/data/LeagueScheduleData.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index 6a05ff1..bdb1556 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -3,7 +3,7 @@ import StaticDateTimes // MARK: Data /// Fundamental building block that keeps track of and enforces assignment rules when building the schedule. -struct LeagueScheduleData: Sendable, ~Copyable { +struct LeagueScheduleData: Sendable, ~Copyable {// let clock = ContinuousClock() let entriesPerMatchup:EntriesPerMatchup let entriesCount:Int From cc5b056af59f1bef3374119c5c25746ed9cfaa4f Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 12:53:15 -0500 Subject: [PATCH 21/33] apply some missing changes --- .../data/RedistributionData.swift | 2 - .../RequestPayload+Extensions.swift | 22 - .../extensions/RequestPayload+Generate.swift | 500 ++++++++++++++++++ .../runtime/GeneralSettings+Runtime.swift | 190 ++++--- .../GeneralSettings+RuntimeProtocol.swift | 68 --- .../util/EntryAssignmentData.swift | 2 +- 6 files changed, 629 insertions(+), 155 deletions(-) create mode 100644 Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift delete mode 100644 Sources/league-scheduling/generated/runtime/GeneralSettings+RuntimeProtocol.swift diff --git a/Sources/league-scheduling/data/RedistributionData.swift b/Sources/league-scheduling/data/RedistributionData.swift index c8eee22..0bea38f 100644 --- a/Sources/league-scheduling/data/RedistributionData.swift +++ b/Sources/league-scheduling/data/RedistributionData.swift @@ -109,7 +109,6 @@ extension RedistributionData { while (assigned < minMatchupsRequired || assigned < maxMovableMatchups) && !assignmentState.availableSlots.isEmpty { guard var redistributable = selectRedistributable( from: redistributables, - assignmentState: assignmentState, generationData: generationData ) else { break } assigned += 1 @@ -133,7 +132,6 @@ extension RedistributionData { extension RedistributionData { private func selectRedistributable( from redistributables: Set, - assignmentState: borrowing AssignmentState, generationData: LeagueGenerationData ) -> Redistributable? { var redistributable:Redistributable? = nil diff --git a/Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift b/Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift index 84fc30b..1f3d3ed 100644 --- a/Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift +++ b/Sources/league-scheduling/generated/extensions/RequestPayload+Extensions.swift @@ -25,26 +25,4 @@ extension RequestPayload { self.divisions = .init(divisions: divisions) self.entries = teams } -} - -// MARK: Calculate max playable matchups -extension RequestPayload { - static func calculateMaximumPlayableMatchups( - gameDays: DayIndex, - entryMatchupsPerGameDay: EntryMatchupsPerGameDay, - teamsCount: Int, - maximumPlayableMatchups: [UInt32] - ) -> [UInt32] { - if maximumPlayableMatchups.isEmpty { - return .init(repeating: gameDays * entryMatchupsPerGameDay, count: teamsCount) - } else if maximumPlayableMatchups.count != teamsCount { - var array = [UInt32](repeating: gameDays * entryMatchupsPerGameDay, count: teamsCount) - for i in 0.. LeagueGenerationResult { + guard gameDays > 0 else { + throw .malformedInput(msg: "'gameDays' needs to be > 0") + } + let divisionsCount:Int + if hasDivisions { + guard divisions.divisions.count > 0 else { + throw .malformedInput(msg: "'divisions' needs to be > 0") + } + divisionsCount = divisions.divisions.count + } else { + divisionsCount = 1 + } + guard entries.count > 0 else { + throw .malformedInput(msg: "'entries' needs to be > 0") + } + if hasIndividualDaySettings { + guard individualDaySettings.days.count == gameDays else { + throw .malformedInput(msg: "'individualDaySettings' size != \(gameDays)") + } + } + guard let defaultGameGap = try validateSettings(kind: "default", settings: settings, fallbackSettings: settings) else { + throw .malformedInput(msg: "missing default 'gameGap' value") + } + if hasIndividualDaySettings { + for (dayIndex, daySettings) in individualDaySettings.days.enumerated() { + if daySettings.hasSettings { + try validateSettings(kind: "for day \(dayIndex), individual day setting", settings: daySettings.settings, fallbackSettings: settings) + } + } + } + let startingDayOfWeek:DayOfWeek + if hasStarts { + guard let starts = StaticDate(htmlDate: starts) else { + throw .malformedHTMLDateInput(key: "starts", value: starts) + } + startingDayOfWeek = starts.dayOfWeek + } else if hasDivisions, let dow = divisions.divisions.first(where: { $0.hasDayOfWeek })?.dayOfWeek, dow <= UInt8.max { + startingDayOfWeek = UInt8(dow) + } else { + startingDayOfWeek = 0 + } + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek + ) + } +} + +extension RequestPayload { + private func generate( + defaultGameGap: GameGap, + divisionsCount: Int, + startingDayOfWeek: DayOfWeek + ) async throws(LeagueError) -> LeagueGenerationResult { + if gameDays <= 64 && settings.timeSlots <= 64 && settings.locations <= 64 && entries.count <= 64 { + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + c: ScheduleConfig< + BitSet64, + BitSet64, + BitSet64, + BitSet64 + >.self + ) + } else if gameDays <= 128 && settings.timeSlots <= 128 && settings.locations <= 128 && entries.count <= 128 { + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + c: ScheduleConfig< + BitSet128, + BitSet128, + BitSet128, + BitSet128 + >.self + ) + } else { + return try await generate( + defaultGameGap: defaultGameGap, + divisionsCount: divisionsCount, + startingDayOfWeek: startingDayOfWeek, + c: ScheduleConfig< + Set, + Set, + Set, + Set + >.self + ) + } + } +} + +extension RequestPayload { + private func generate( + defaultGameGap: GameGap, + divisionsCount: Int, + startingDayOfWeek: DayOfWeek, + c: Config.Type + ) async throws(LeagueError) -> LeagueGenerationResult { + let divisionDefaults:DivisionDefaults = loadDivisionDefaults(divisionsCount: divisionsCount) + var teamsForDivision = [Int](repeating: 0, count: divisionsCount) + let entries = try parseEntries( + divisionsCount: divisionsCount, + teams: entries, + teamsForDivision: &teamsForDivision, + divisionDefaults: divisionDefaults + ) + let divisions = try parseDivisions( + divisionsCount: divisionsCount, + locations: settings.locations, + divisionGameDays: divisionDefaults.gameDays, + defaultGameGap: defaultGameGap, + fallbackDayOfWeek: startingDayOfWeek, + teamsForDivision: teamsForDivision + ) + let correctMaximumPlayableMatchups = Self.calculateMaximumPlayableMatchups( + gameDays: gameDays, + entryMatchupsPerGameDay: settings.entryMatchupsPerGameDay, + teamsCount: entries.count, + maximumPlayableMatchups: settings.maximumPlayableMatchups.array + ) + + let timesSet = Config.TimeSet(0..( + gameGap: defaultGameGap, + timeSlots: settings.timeSlots, + startingTimes: settings.startingTimes.times, + entriesPerLocation: settings.entriesPerLocation, + locations: settings.locations, + entryMatchupsPerGameDay: settings.entryMatchupsPerGameDay, + maximumPlayableMatchups: correctMaximumPlayableMatchups, + matchupDuration: settings.matchupDuration, + locationTimeExclusivities: defaultTimeExclusivities, + locationTravelDurations: defaultTravelDurations, + balanceTimeStrictness: settings.balanceTimeStrictness, + balancedTimes: balancedTimes, + balanceLocationStrictness: settings.balanceLocationStrictness, + balancedLocations: balancedLocations, + redistributionSettings: settings.hasRedistributionSettings ? settings.redistributionSettings : nil, + flags: settings.flags + ) + ) + } +} + +extension RequestPayload { + #if SpecializeScheduleConfiguration + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif + private func generate( + divisions: [Config.DivisionRuntime], + entries: [Config.EntryRuntime], + correctMaximumPlayableMatchups: [UInt32], + general: GeneralSettings.Runtime + ) async throws(LeagueError) -> LeagueGenerationResult { + let daySettings = try parseDaySettings( + general: general, + correctMaximumPlayableMatchups: correctMaximumPlayableMatchups, + entries: entries + ) + let runtime = RequestPayload.Runtime( + constraints: generationConstraints, + gameDays: gameDays, + divisions: divisions, + entries: entries, + general: general, + daySettings: daySettings + ) + return await runtime.generate() + } +} + +// MARK: Division defaults +extension RequestPayload { + struct DivisionDefaults: Sendable, ~Copyable { + let gameDays:[DaySet] + let byes:[DaySet] + let gameTimes:[[Times]] + let gameLocations:[[Locations]] + } +} + +// MARK: Load division defaults +extension RequestPayload { + private func loadDivisionDefaults< + DaySet: SetOfDayIndexes, + Times: SetOfTimeIndexes, + Locations: SetOfLocationIndexes + >( + divisionsCount: Int + ) -> DivisionDefaults { + var gameDays = [DaySet]() + var byes = [DaySet]() + var gameTimes = [[Times]]() + var gameLocations = [[Locations]]() + gameDays.reserveCapacity(divisionsCount) + byes.reserveCapacity(divisionsCount) + gameTimes.reserveCapacity(divisionsCount) + gameLocations.reserveCapacity(divisionsCount) + + let getTimesFunc:@Sendable (RequestPayload, DayIndex, TimeIndex) -> TimeIndex + let getLocationsFunc:@Sendable (RequestPayload, DayIndex, LocationIndex) -> LocationIndex + if hasIndividualDaySettings { + getTimesFunc = Self.individualDayTimes + getLocationsFunc = Self.individualDayLocations + } else { + getTimesFunc = { _, _, fallback in return fallback } + getLocationsFunc = { _, _, fallback in return fallback } + } + if hasDivisions { + for division in divisions.divisions { + let targetGameDays:DaySet + if division.hasGameDays { + targetGameDays = .init(division.gameDays.gameDayIndexes) + } else { + targetGameDays = .init(0.. TimeIndex { + let daySettings = payload.individualDaySettings.days[unchecked: dayIndex] + guard daySettings.hasSettings, daySettings.settings.hasTimeSlots else { return fallback } + return daySettings.settings.timeSlots + } + private static func individualDayLocations( + payload: RequestPayload, + dayIndex: DayIndex, + fallback: LocationIndex + ) -> LocationIndex { + let daySettings = payload.individualDaySettings.days[unchecked: dayIndex] + guard daySettings.hasSettings, daySettings.settings.hasLocations else { return fallback } + return TimeIndex(daySettings.settings.locations) + } +} + +// MARK: Parse teams +extension RequestPayload { + private func parseEntries( + divisionsCount: Int, + teams: [Entry], + teamsForDivision: inout [Int], + divisionDefaults: borrowing DivisionDefaults + ) throws(LeagueError) -> [Entry.Runtime] { + var entries = [Entry.Runtime]() + entries.reserveCapacity(teams.count) + for (i, team) in teams.enumerated() { + if team.hasGameDayTimes { + guard team.gameDayTimes.times.count == gameDays else { + throw .malformedInput(msg: "'gameTimes' size != \(gameDays) for team at index \(i)") + } + } + if team.hasGameDayLocations { + guard team.gameDayLocations.locations.count == gameDays else { + throw .malformedInput(msg: "'gameLocations' size != \(gameDays) for team at index \(i)") + } + } + if team.hasMatchupsPerGameDay { + guard team.matchupsPerGameDay.gameDayMatchups.count == gameDays else { + throw .malformedInput(msg: "'matchupsPerGameDay.gameDayMatchups' size != \(gameDays) for team at index \(i)") + } + } + let division = min(team.division, UInt32(divisionsCount - 1)) + teamsForDivision[Int(division)] += 1 + entries.append(team.runtime( + id: Entry.IDValue(i), + division: division, + defaultGameDays: divisionDefaults.gameDays[unchecked: division], + defaultByes: divisionDefaults.byes[unchecked: division], + defaultGameTimes: divisionDefaults.gameTimes[unchecked: division], + defaultGameLocations: divisionDefaults.gameLocations[unchecked: division] + )) + } + return entries + } +} + +// MARK: Parse divisions +extension RequestPayload { + private func parseDivisions( + divisionsCount: Int, + locations: LocationIndex, + divisionGameDays: [DaySet], + defaultGameGap: GameGap, + fallbackDayOfWeek: DayOfWeek, + teamsForDivision: [Int] + ) throws(LeagueError) -> [Division.Runtime] { + var runtimeDivisions = [Division.Runtime]() + runtimeDivisions.reserveCapacity(divisionsCount) + if hasDivisions { + for (i, division) in divisions.divisions.enumerated() { + if division.hasOpponents { + guard division.opponents.divisionOpponentIds.count > 0 else { + throw .malformedInput(msg: "'opponents' size needs to be > 0 for division at index \(i)") + } + } + if division.hasGameDayTimes { + guard division.gameDayTimes.times.count == gameDays else { + throw .malformedInput(msg: "'gameDayTimes' size != \(gameDays) for division at index \(i)") + } + } + if division.hasGameDayLocations { + guard division.gameDayLocations.locations.count == gameDays else { + throw .malformedInput(msg: "'gameDayLocations' size != \(gameDays) for division at index \(i)") + } + } + if division.hasTravelDurations { + guard division.travelDurations.locations.count == locations else { + throw .malformedInput(msg: "'travelDurations' size != \(locations) for division at index \(i)") + } + } + if division.hasLocationTimeExclusivities { + guard division.locationTimeExclusivities.locations.count == locations else { + throw .malformedInput(msg: "'locationTimeExclusivities' size != \(locations) for division at index \(i)") + } + } + let maxSameOpponentMatchups = try calculateMaximumSameOpponentMatchupsCap( + gameDays: gameDays, + entryMatchupsPerGameDay: settings.entryMatchupsPerGameDay, + entriesCount: teamsForDivision[Int(i)] + ) + try runtimeDivisions.append(division.runtime( + defaultGameDays: divisionGameDays[unchecked: i], + defaultGameGap: defaultGameGap, + fallbackDayOfWeek: fallbackDayOfWeek, + fallbackMaxSameOpponentMatchups: maxSameOpponentMatchups + )) + } + } else { + let maxSameOpponentMatchups = try calculateMaximumSameOpponentMatchupsCap( + gameDays: gameDays, + entryMatchupsPerGameDay: settings.entryMatchupsPerGameDay, + entriesCount: teamsForDivision[Int(0)] + ) + runtimeDivisions.append(.init( + dayOfWeek: fallbackDayOfWeek, + gameDays: divisionGameDays[unchecked: 0], + gameGaps: .init(repeating: defaultGameGap, count: gameDays), + maxSameOpponentMatchups: maxSameOpponentMatchups + )) + } + return runtimeDivisions + } +} + +// MARK: Parse day settings +extension RequestPayload { + #if SpecializeScheduleConfiguration + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif + private func parseDaySettings( + general: GeneralSettings.Runtime, + correctMaximumPlayableMatchups: [UInt32], + entries: [Config.EntryRuntime] + ) throws(LeagueError) -> [GeneralSettings.Runtime] { + var daySettings = [GeneralSettings.Runtime]() + daySettings.reserveCapacity(gameDays) + if hasIndividualDaySettings { + for dayIndex in 0.. [UInt32] { + if maximumPlayableMatchups.isEmpty { + return .init(repeating: gameDays * entryMatchupsPerGameDay, count: teamsCount) + } else if maximumPlayableMatchups.count != teamsCount { + var array = [UInt32](repeating: gameDays * entryMatchupsPerGameDay, count: teamsCount) + for i in 0.., BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif + init( + gameGap: GameGap, + timeSlots: TimeIndex, + startingTimes: [StaticTime], + entriesPerLocation: EntriesPerMatchup, + locations: LocationIndex, + entryMatchupsPerGameDay: EntryMatchupsPerGameDay, + maximumPlayableMatchups: [UInt32], + matchupDuration: MatchupDuration, + locationTimeExclusivities: [Config.TimeSet]?, + locationTravelDurations: [[MatchupDuration]]?, + balanceTimeStrictness: BalanceStrictness, + balancedTimes: Config.TimeSet, + balanceLocationStrictness: BalanceStrictness, + balancedLocations: Config.LocationSet, + redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, + flags: UInt32 + ) { + self.gameGap = gameGap + self.timeSlots = timeSlots + self.startingTimes = startingTimes + self.entriesPerLocation = entriesPerLocation + self.locations = locations + self.defaultMaxEntryMatchupsPerGameDay = entryMatchupsPerGameDay + self.maximumPlayableMatchups = maximumPlayableMatchups + self.matchupDuration = matchupDuration + self.locationTimeExclusivities = locationTimeExclusivities + self.locationTravelDurations = locationTravelDurations + self.balanceTimeStrictness = balanceTimeStrictness + self.balancedTimes = balancedTimes + self.balanceLocationStrictness = balanceLocationStrictness + self.balancedLocations = balancedLocations + self.redistributionSettings = redistributionSettings + self.flags = flags + } +} + +// MARK: Available slots +extension GeneralSettings.Runtime { + func availableSlots() -> Set { + var slots = Set(minimumCapacity: timeSlots * locations) + if let exclusivities = locationTimeExclusivities { + for location in 0.. Set { - var slots = Set(minimumCapacity: timeSlots * locations) - if let exclusivities = locationTimeExclusivities { - for location in 0.. Bool { + flags & UInt32(1 << flag.rawValue) != 0 } - func containsBalancedTime(_ timeSlot: TimeIndex) -> Bool { - balancedTimes.contains(timeSlot) + var optimizeTimes: Bool { + isFlag(.optimizeTimes) } - func containsBalancedLocation(_ location: LocationIndex) -> Bool { - balancedLocations.contains(location) + + var prioritizeEarlierTimes: Bool { + isFlag(.prioritizeEarlierTimes) } - #if SpecializeScheduleConfiguration - @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) - @_specialize(where Config == ScheduleConfig, Set, Set, Set>) - #endif + var prioritizeHomeAway: Bool { + isFlag(.prioritizeHomeAway) + } + + var balanceHomeAway: Bool { + isFlag(.balanceHomeAway) + } + + var sameLocationIfB2B: Bool { + isFlag(.sameLocationIfBackToBack) + } +} + +// MARK: Compute settings +extension GeneralSettings.Runtime { init( - gameGap: GameGap, - timeSlots: TimeIndex, - startingTimes: [StaticTime], - entriesPerLocation: EntriesPerMatchup, - locations: LocationIndex, - entryMatchupsPerGameDay: EntryMatchupsPerGameDay, - maximumPlayableMatchups: [UInt32], - matchupDuration: MatchupDuration, - locationTimeExclusivities: [Config.TimeSet]?, - locationTravelDurations: [[MatchupDuration]]?, - balanceTimeStrictness: BalanceStrictness, - balancedTimes: Config.TimeSet, - balanceLocationStrictness: BalanceStrictness, - balancedLocations: Config.LocationSet, - redistributionSettings: LitLeagues_Leagues_RedistributionSettings?, - flags: UInt32 + protobuf: GeneralSettings + ) throws(LeagueError) { + guard let gameGap = GameGap(htmlInputValue: protobuf.gameGap) else { + throw .malformedInput(msg: "invalid GameGap htmlInputValue: \(protobuf.gameGap)") + } + self.init(gameGap: gameGap, protobuf: protobuf) + } + + /// Modifies `timeSlots` and `startingTimes` taking into account current settings. + mutating func computeSettings( + day: DayIndex, + entries: [Config.EntryRuntime] ) { - self.gameGap = gameGap - self.timeSlots = timeSlots - self.startingTimes = startingTimes - self.entriesPerLocation = entriesPerLocation - self.locations = locations - self.defaultMaxEntryMatchupsPerGameDay = entryMatchupsPerGameDay - self.maximumPlayableMatchups = maximumPlayableMatchups - self.matchupDuration = matchupDuration - self.locationTimeExclusivities = locationTimeExclusivities - self.locationTravelDurations = locationTravelDurations - self.balanceTimeStrictness = balanceTimeStrictness - self.balancedTimes = balancedTimes - self.balanceLocationStrictness = balanceLocationStrictness - self.balancedLocations = balancedLocations - self.redistributionSettings = redistributionSettings - self.flags = flags + if optimizeTimes { + var maxMatchupsPlayedToday:LocationIndex = 0 + for entry in entries { + if entry.gameDays.contains(day) && !entry.byes.contains(day) { + maxMatchupsPlayedToday += entry.maxMatchupsForGameDay(day: day, fallback: defaultMaxEntryMatchupsPerGameDay) + } + } + maxMatchupsPlayedToday /= entriesPerLocation + let filledTimeSlots = optimalTimeSlots( + availableTimeSlots: timeSlots, + locations: locations, + matchupsCount: maxMatchupsPlayedToday + ) + while filledTimeSlots < timeSlots { + timeSlots -= 1 + } + while filledTimeSlots < startingTimes.count { + startingTimes.removeLast() + } + } } } \ No newline at end of file diff --git a/Sources/league-scheduling/generated/runtime/GeneralSettings+RuntimeProtocol.swift b/Sources/league-scheduling/generated/runtime/GeneralSettings+RuntimeProtocol.swift deleted file mode 100644 index 010d492..0000000 --- a/Sources/league-scheduling/generated/runtime/GeneralSettings+RuntimeProtocol.swift +++ /dev/null @@ -1,68 +0,0 @@ - -import StaticDateTimes - -// MARK: Flags -extension GeneralSettings.Runtime { - func isFlag(_ flag: SettingFlags) -> Bool { - flags & UInt32(1 << flag.rawValue) != 0 - } - - var optimizeTimes: Bool { - isFlag(.optimizeTimes) - } - - var prioritizeEarlierTimes: Bool { - isFlag(.prioritizeEarlierTimes) - } - - var prioritizeHomeAway: Bool { - isFlag(.prioritizeHomeAway) - } - - var balanceHomeAway: Bool { - isFlag(.balanceHomeAway) - } - - var sameLocationIfB2B: Bool { - isFlag(.sameLocationIfBackToBack) - } -} - -// MARK: Compute settings -extension GeneralSettings.Runtime { - init( - protobuf: GeneralSettings - ) throws(LeagueError) { - guard let gameGap = GameGap(htmlInputValue: protobuf.gameGap) else { - throw .malformedInput(msg: "invalid GameGap htmlInputValue: \(protobuf.gameGap)") - } - self.init(gameGap: gameGap, protobuf: protobuf) - } - - /// Modifies `timeSlots` and `startingTimes` taking into account current settings. - mutating func computeSettings( - day: DayIndex, - entries: [Config.EntryRuntime] - ) { - if optimizeTimes { - var maxMatchupsPlayedToday:LocationIndex = 0 - for entry in entries { - if entry.gameDays.contains(day) && !entry.byes.contains(day) { - maxMatchupsPlayedToday += entry.maxMatchupsForGameDay(day: day, fallback: defaultMaxEntryMatchupsPerGameDay) - } - } - maxMatchupsPlayedToday /= entriesPerLocation - let filledTimeSlots = optimalTimeSlots( - availableTimeSlots: timeSlots, - locations: locations, - matchupsCount: maxMatchupsPlayedToday - ) - while filledTimeSlots < timeSlots { - timeSlots -= 1 - } - while filledTimeSlots < startingTimes.count { - startingTimes.removeLast() - } - } - } -} \ No newline at end of file diff --git a/Sources/league-scheduling/util/EntryAssignmentData.swift b/Sources/league-scheduling/util/EntryAssignmentData.swift index 6a1920f..6eaab31 100644 --- a/Sources/league-scheduling/util/EntryAssignmentData.swift +++ b/Sources/league-scheduling/util/EntryAssignmentData.swift @@ -24,7 +24,7 @@ struct EntryAssignmentData: Sendable { /// Number of times an entry was assigned to play at home or away against another entry. /// - /// - Usage: [opponent `Entry.IDValue`: [`LeagueSchedule.HomeAwayValue`]] + /// - Usage: [opponent `Entry.IDValue`: [`HomeAwayValue`]] var assignedEntryHomeAways:[HomeAwayValue] /// Maximum number of times an entry can play against another entry. From f279864b2d2fc9d3eb360dc0e764169a5eb68f30 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Mon, 16 Mar 2026 13:01:37 -0500 Subject: [PATCH 22/33] replace `SetOfDayIndexes`, `SetOfTimeIndexes` and `SetOfLocationIndexes` with a typealias to the new `SetOfUInt32` protocol --- Sources/league-scheduling/util/AbstractSet.swift | 12 ++++++------ Sources/league-scheduling/util/BitSet128.swift | 4 +--- Sources/league-scheduling/util/BitSet64.swift | 4 +--- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/Sources/league-scheduling/util/AbstractSet.swift b/Sources/league-scheduling/util/AbstractSet.swift index b1f4489..b22410f 100644 --- a/Sources/league-scheduling/util/AbstractSet.swift +++ b/Sources/league-scheduling/util/AbstractSet.swift @@ -32,9 +32,11 @@ protocol AbstractSet: Sendable, ~Copyable { func forEachWithReturn(_ body: (Element) throws -> Result?) rethrows -> Result? } -protocol SetOfDayIndexes: AbstractSet, ~Copyable where Element == DayIndex {} -protocol SetOfTimeIndexes: AbstractSet, ~Copyable where Element == TimeIndex {} -protocol SetOfLocationIndexes: AbstractSet, ~Copyable where Element == LocationIndex {} +protocol SetOfUInt32: AbstractSet, ~Copyable where Element == UInt32 {} + +typealias SetOfDayIndexes = SetOfUInt32 +typealias SetOfTimeIndexes = SetOfUInt32 +typealias SetOfLocationIndexes = SetOfUInt32 protocol SetOfEntryIDs: AbstractSet, ~Copyable where Element == Entry.IDValue { /// - Returns: The available matchup pairs that can play for the `day`. @@ -83,9 +85,7 @@ extension Set: AbstractSet { } } -extension Set: SetOfDayIndexes {} -extension Set: SetOfTimeIndexes {} -extension Set: SetOfLocationIndexes {} +extension Set: SetOfUInt32 {} extension Set: SetOfEntryIDs { func availableMatchupPairs( diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index c3e0502..8ee2dca 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -110,9 +110,7 @@ extension BitSet128 { // MARK: AbstractSet extension BitSet128: AbstractSet {} -extension BitSet128: SetOfDayIndexes where Element == TimeIndex {} -extension BitSet128: SetOfTimeIndexes where Element == TimeIndex {} -extension BitSet128: SetOfLocationIndexes where Element == LocationIndex {} +extension BitSet128: SetOfUInt32 where Element == UInt32 {} extension BitSet128: SetOfEntryIDs where Element == Entry.IDValue { func availableMatchupPairs( diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index 3517bc3..9751b3c 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -116,9 +116,7 @@ extension BitSet64 { // MARK: AbstractSet extension BitSet64: AbstractSet {} -extension BitSet64: SetOfDayIndexes where Element == TimeIndex {} -extension BitSet64: SetOfTimeIndexes where Element == TimeIndex {} -extension BitSet64: SetOfLocationIndexes where Element == LocationIndex {} +extension BitSet64: SetOfUInt32 where Element == UInt32 {} extension BitSet64: SetOfEntryIDs where Element == Entry.IDValue { func availableMatchupPairs( From 7c490488aba543579201ce0462426031e668186a Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 06:23:21 -0500 Subject: [PATCH 23/33] add bit set unit tests --- .../league-scheduling/util/BitSet128.swift | 4 + Sources/league-scheduling/util/BitSet64.swift | 4 + .../BitSet128Tests.swift | 117 ++++++++++++++++++ .../LeagueSchedulingTests/BitSet64Tests.swift | 109 ++++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 Tests/LeagueSchedulingTests/BitSet128Tests.swift create mode 100644 Tests/LeagueSchedulingTests/BitSet64Tests.swift diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index 8ee2dca..440ea7a 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -8,6 +8,10 @@ struct BitSet128: Sendable { storage = 0 } + init(storage: UInt128) { + self.storage = storage + } + init(_ collection: some Collection) { storage = 0 for e in collection { diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index 9751b3c..3a30390 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -8,6 +8,10 @@ struct BitSet64: Sendable { storage = 0 } + init(storage: UInt64) { + self.storage = storage + } + init(_ collection: some Collection) { storage = 0 for e in collection { diff --git a/Tests/LeagueSchedulingTests/BitSet128Tests.swift b/Tests/LeagueSchedulingTests/BitSet128Tests.swift new file mode 100644 index 0000000..c763ad2 --- /dev/null +++ b/Tests/LeagueSchedulingTests/BitSet128Tests.swift @@ -0,0 +1,117 @@ + +@testable import LeagueScheduling +import Testing + +struct BitSet128Tests { + @Test + func bitSet128InsertMember() { + var s = BitSet128() + for i in 0...init(storage: .max) + for i in 0...init(storage: 0x1010101010101010_1010101010101010) + #expect(!s.contains(0)) + #expect(!s.contains(1)) + #expect(!s.contains(2)) + #expect(!s.contains(3)) + #expect(s.contains(4)) + #expect(!s.contains(5)) + #expect(!s.contains(6)) + #expect(!s.contains(7)) + #expect(!s.contains(8)) + + #expect(s.contains(12)) + #expect(s.contains(20)) + #expect(s.contains(28)) + #expect(s.contains(36)) + #expect(s.contains(44)) + #expect(s.contains(52)) + #expect(s.contains(60)) + #expect(s.contains(68)) + #expect(s.contains(76)) + #expect(s.contains(84)) + #expect(s.contains(92)) + #expect(s.contains(100)) + #expect(s.contains(108)) + #expect(s.contains(116)) + #expect(s.contains(124)) + } + + @Test + func bitSet128RandomElement() { + var s = BitSet128() + #expect(s.randomElement() == nil) + + s.insertMember(8) + #expect(s.randomElement() == 8) + + s.removeMember(8) + #expect(s.randomElement() == nil) + + s.insertMember(128) + #expect(s.randomElement() == nil) + } + + @Test + func bitSet128ForEach() { + var s = BitSet128() + s.forEach { _ in + #expect(Bool(false)) + } + + s.insertMember(0) + s.insertMember(32) + s.insertMember(127) + s.forEach { i in + #expect(i == 0 || i == 32 || i == 127) + } + #expect(s.contains(0)) + #expect(s.contains(32)) + #expect(s.contains(127)) + } + + @Test + func bitSet128Count() { + var s = BitSet128() + #expect(s.count == 0) + #expect(s.isEmpty) + + s.insertMember(0) + #expect(s.count == 1) + #expect(!s.isEmpty) + + s.insertMember(128) + #expect(s.count == 1) + #expect(!s.isEmpty) + + s.insertMember(127) + #expect(s.count == 2) + #expect(!s.isEmpty) + + s.removeMember(0) + #expect(s.count == 1) + #expect(!s.isEmpty) + } + + @Test + func bitSet128FormUnion() { + var s = BitSet128(storage: 0x1010101010101010) + s.formUnion(.init(storage: 0x0101010101010101)) + #expect(s == .init(storage: 0x1111111111111111)) + } +} \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/BitSet64Tests.swift b/Tests/LeagueSchedulingTests/BitSet64Tests.swift new file mode 100644 index 0000000..95bedc1 --- /dev/null +++ b/Tests/LeagueSchedulingTests/BitSet64Tests.swift @@ -0,0 +1,109 @@ + +@testable import LeagueScheduling +import Testing + +struct BitSet64Tests { + @Test + func bitSet64InsertMember() { + var s = BitSet64() + for i in 0...init(storage: .max) + for i in 0...init(storage: 0x1010101010101010) + #expect(!s.contains(0)) + #expect(!s.contains(1)) + #expect(!s.contains(2)) + #expect(!s.contains(3)) + #expect(s.contains(4)) + #expect(!s.contains(5)) + #expect(!s.contains(6)) + #expect(!s.contains(7)) + #expect(!s.contains(8)) + + #expect(s.contains(12)) + #expect(s.contains(20)) + #expect(s.contains(28)) + #expect(s.contains(36)) + #expect(s.contains(44)) + #expect(s.contains(52)) + #expect(s.contains(60)) + } + + @Test + func bitSet64RandomElement() { + var s = BitSet64() + #expect(s.randomElement() == nil) + + s.insertMember(8) + #expect(s.randomElement() == 8) + + s.removeMember(8) + #expect(s.randomElement() == nil) + + s.insertMember(65) + #expect(s.randomElement() == nil) + } + + @Test + func bitSet64ForEach() { + var s = BitSet64() + s.forEach { _ in + #expect(Bool(false)) + } + + s.insertMember(0) + s.insertMember(32) + s.insertMember(63) + s.forEach { i in + #expect(i == 0 || i == 32 || i == 63) + } + #expect(s.contains(0)) + #expect(s.contains(32)) + #expect(s.contains(63)) + } + + @Test + func bitSet64Count() { + var s = BitSet64() + #expect(s.count == 0) + #expect(s.isEmpty) + + s.insertMember(0) + #expect(s.count == 1) + #expect(!s.isEmpty) + + s.insertMember(64) + #expect(s.count == 1) + #expect(!s.isEmpty) + + s.insertMember(63) + #expect(s.count == 2) + #expect(!s.isEmpty) + + s.removeMember(0) + #expect(s.count == 1) + #expect(!s.isEmpty) + } + + @Test + func bitSet64FormUnion() { + var s = BitSet64(storage: 0x1010101010101010) + s.formUnion(.init(storage: 0x0101010101010101)) + #expect(s == .init(storage: 0x1111111111111111)) + } +} \ No newline at end of file From 4c3a0206f2e087a75391fbfd726764bb73c7b3a8 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 06:34:29 -0500 Subject: [PATCH 24/33] revert minor performance change when selecting b2b slots --- .../data/selectSlot/SelectSlotB2B.swift | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift index 03b0252..619e485 100644 --- a/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift +++ b/Sources/league-scheduling/data/selectSlot/SelectSlotB2B.swift @@ -12,8 +12,9 @@ struct SelectSlotB2B: SelectSlotProtocol, ~Copyable { playableSlots: inout Set ) -> AvailableSlot? { filter( - team1PlaysAtTimes: playsAtTimes[unchecked: team1], - team2PlaysAtTimes: playsAtTimes[unchecked: team2], + team1: team1, + team2: team2, + playsAtTimes: playsAtTimes, playableSlots: &playableSlots ) return SelectSlotNormal.select( @@ -29,12 +30,13 @@ struct SelectSlotB2B: SelectSlotProtocol, ~Copyable { extension SelectSlotB2B { /// Mutates `playableSlots`, if `team1` AND `team2` haven't played already, so it only contains the first slots applicable for a matchup block. private func filter( - team1PlaysAtTimes: TimeSet, - team2PlaysAtTimes: TimeSet, + team1: Entry.IDValue, + team2: Entry.IDValue, + playsAtTimes: ContiguousArray, playableSlots: inout Set ) { //print("filterSlotBack2Back;playsAtTimes[unchecked: team1].isEmpty=\(playsAtTimes[unchecked: team1].isEmpty);playsAtTimes[unchecked: team2].isEmpty=\(playsAtTimes[unchecked: team2].isEmpty)") - if team1PlaysAtTimes.isEmpty && team2PlaysAtTimes.isEmpty { + if playsAtTimes[unchecked: team1].isEmpty && playsAtTimes[unchecked: team2].isEmpty { playableSlots = playableSlots.filter({ $0.time % entryMatchupsPerGameDay == 0 }) } } From 493e893049dde16b6ae5fe1f317d34a667687388 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 08:34:01 -0500 Subject: [PATCH 25/33] use `Config.TimeSet` when assigning b2b slots --- Sources/league-scheduling/data/AssignSlotsB2B.swift | 4 ++-- Sources/league-scheduling/data/LeagueScheduleData.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/league-scheduling/data/AssignSlotsB2B.swift b/Sources/league-scheduling/data/AssignSlotsB2B.swift index 52569c8..ecc6e74 100644 --- a/Sources/league-scheduling/data/AssignSlotsB2B.swift +++ b/Sources/league-scheduling/data/AssignSlotsB2B.swift @@ -12,7 +12,7 @@ extension LeagueScheduleData { // TODO: pick the optimal combination that should be selected? combinationLoop: for combination in allowedDivisionCombinations { var assignedSlots = Set() - var combinationTimeAllocations:ContiguousArray> = .init( + var combinationTimeAllocations:ContiguousArray = .init( repeating: .init(), count: combination.first?.count ?? 10 ) @@ -34,7 +34,7 @@ extension LeagueScheduleData { #if LOG print("assignSlots;b2b;division=\(division);divisionCombination=\(divisionCombination);matchups.count=\(assignmentState.matchups.count);availableSlots=\(assignmentState.availableSlots.map({ $0.description }));remainingAllocations=\(assignmentState.remainingAllocations.map { $0.map({ $0.description }) })") #endif - var disallowedTimes = BitSet64() + var disallowedTimes = Config.TimeSet() for (divisionCombinationIndex, amount) in divisionCombination.enumerated() { guard amount > 0 else { continue } let combinationTimeAllocation = combinationTimeAllocations[divisionCombinationIndex] diff --git a/Sources/league-scheduling/data/LeagueScheduleData.swift b/Sources/league-scheduling/data/LeagueScheduleData.swift index bdb1556..6a05ff1 100644 --- a/Sources/league-scheduling/data/LeagueScheduleData.swift +++ b/Sources/league-scheduling/data/LeagueScheduleData.swift @@ -3,7 +3,7 @@ import StaticDateTimes // MARK: Data /// Fundamental building block that keeps track of and enforces assignment rules when building the schedule. -struct LeagueScheduleData: Sendable, ~Copyable {// +struct LeagueScheduleData: Sendable, ~Copyable { let clock = ContinuousClock() let entriesPerMatchup:EntriesPerMatchup let entriesCount:Int From 15798db58835ff2e387abe7a0b6999c8e44e9621 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 09:16:26 -0500 Subject: [PATCH 26/33] unit test minor fixes --- .../schedules/util/ScheduleTestsProtocol.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift index e92da57..1d0c97c 100644 --- a/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift +++ b/Tests/LeagueSchedulingTests/schedules/util/ScheduleTestsProtocol.swift @@ -17,7 +17,7 @@ extension ScheduleTestsProtocol { locations: LocationIndex, teams: Int, homeLocations: ContiguousArray = [], - byes: ContiguousArray = [] + byes: ContiguousArray = [] ) -> [UnitTestScheduleConfig.EntryRuntime] { let playsOn = Array(repeating: UnitTestScheduleConfig.DaySet(0.. = .init(0.. = .init(0...init( gameGap: gameGaps, timeSlots: TimeIndex(startingTimes.count), From ea356fa32e4ff85dfdfbf5aa37a399f50d0d7c89 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 09:17:01 -0500 Subject: [PATCH 27/33] remove `PlaysAtTimes` and `PlaysAtLocations` typealiases --- Sources/league-scheduling/typealiases.swift | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/Sources/league-scheduling/typealiases.swift b/Sources/league-scheduling/typealiases.swift index a964f48..818b499 100644 --- a/Sources/league-scheduling/typealiases.swift +++ b/Sources/league-scheduling/typealiases.swift @@ -70,16 +70,6 @@ typealias MaximumTimeAllocations = ContiguousArray> /// - Usage: [`Entry.IDValue`: [`LocationIndex`: `maximum allowed at LocationIndex`]] typealias MaximumLocationAllocations = ContiguousArray> -/// Times where an entry has already played at for the `day`. -/// -/// - Usage: [`Entry.IDValue`: `Set`] -typealias PlaysAtTimes = ContiguousArray> - -/// Locations where an entry has already played at for the `day`. -/// -/// - Usage: [`Entry.IDValue`: `Set`] -typealias PlaysAtLocations = ContiguousArray> - /// Slots where an entry has already played at for the `day`. /// /// - Usage: [`Entry.IDValue`: `Set`] From 561029638dd3bb8d6eedb27b73b95d13b6f68c77 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 09:31:31 -0500 Subject: [PATCH 28/33] add unit tests for bit set `removeAll(where:)` --- Tests/LeagueSchedulingTests/BitSet128Tests.swift | 10 ++++++++++ Tests/LeagueSchedulingTests/BitSet64Tests.swift | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/Tests/LeagueSchedulingTests/BitSet128Tests.swift b/Tests/LeagueSchedulingTests/BitSet128Tests.swift index c763ad2..34f0fc2 100644 --- a/Tests/LeagueSchedulingTests/BitSet128Tests.swift +++ b/Tests/LeagueSchedulingTests/BitSet128Tests.swift @@ -114,4 +114,14 @@ struct BitSet128Tests { s.formUnion(.init(storage: 0x0101010101010101)) #expect(s == .init(storage: 0x1111111111111111)) } + + @Test + func bitSet128RemoveAllWhere() { + var s = BitSet128(storage: .max) + s.removeAll(where: { $0 % 2 == 0 }) + #expect(s.storage == 0xAAAAAAAAAAAAAAAA_AAAAAAAAAAAAAAAA) + + s.removeAll(where: { $0 % 1 == 0}) + #expect(s.storage == 0) + } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/BitSet64Tests.swift b/Tests/LeagueSchedulingTests/BitSet64Tests.swift index 95bedc1..4ca1116 100644 --- a/Tests/LeagueSchedulingTests/BitSet64Tests.swift +++ b/Tests/LeagueSchedulingTests/BitSet64Tests.swift @@ -106,4 +106,14 @@ struct BitSet64Tests { s.formUnion(.init(storage: 0x0101010101010101)) #expect(s == .init(storage: 0x1111111111111111)) } + + @Test + func bitSet64RemoveAllWhere() { + var s = BitSet64(storage: .max) + s.removeAll(where: { $0 % 2 == 0 }) + #expect(s.storage == 0xAAAAAAAAAAAAAAAA) + + s.removeAll(where: { $0 % 1 == 0}) + #expect(s.storage == 0) + } } \ No newline at end of file From 8a2c5b40c0826f387f7d8f49bf5ccfcb30e2d9b7 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 09:44:05 -0500 Subject: [PATCH 29/33] improve spacing for bit set `removeAll(where:)` unit tests --- Tests/LeagueSchedulingTests/BitSet128Tests.swift | 2 +- Tests/LeagueSchedulingTests/BitSet64Tests.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/LeagueSchedulingTests/BitSet128Tests.swift b/Tests/LeagueSchedulingTests/BitSet128Tests.swift index 34f0fc2..04897c7 100644 --- a/Tests/LeagueSchedulingTests/BitSet128Tests.swift +++ b/Tests/LeagueSchedulingTests/BitSet128Tests.swift @@ -121,7 +121,7 @@ struct BitSet128Tests { s.removeAll(where: { $0 % 2 == 0 }) #expect(s.storage == 0xAAAAAAAAAAAAAAAA_AAAAAAAAAAAAAAAA) - s.removeAll(where: { $0 % 1 == 0}) + s.removeAll(where: { $0 % 1 == 0 }) #expect(s.storage == 0) } } \ No newline at end of file diff --git a/Tests/LeagueSchedulingTests/BitSet64Tests.swift b/Tests/LeagueSchedulingTests/BitSet64Tests.swift index 4ca1116..82e1173 100644 --- a/Tests/LeagueSchedulingTests/BitSet64Tests.swift +++ b/Tests/LeagueSchedulingTests/BitSet64Tests.swift @@ -113,7 +113,7 @@ struct BitSet64Tests { s.removeAll(where: { $0 % 2 == 0 }) #expect(s.storage == 0xAAAAAAAAAAAAAAAA) - s.removeAll(where: { $0 % 1 == 0}) + s.removeAll(where: { $0 % 1 == 0 }) #expect(s.storage == 0) } } \ No newline at end of file From f06933b0ecc47a28dd907c83a32fae59802daa51 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 13:04:16 -0500 Subject: [PATCH 30/33] fallback to the default generation constraints --- .../extensions/RequestPayload+Generate.swift | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift b/Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift index 68a338b..a78e70e 100644 --- a/Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift +++ b/Sources/league-scheduling/generated/extensions/RequestPayload+Generate.swift @@ -200,8 +200,24 @@ extension RequestPayload { correctMaximumPlayableMatchups: correctMaximumPlayableMatchups, entries: entries ) + // always fallback to the defaults since computation can be expensive; handle with care + var constraints = GenerationConstraints.default + if hasGenerationConstraints { + if generationConstraints.hasTimeoutDelay { + constraints.timeoutDelay = generationConstraints.timeoutDelay + } + if generationConstraints.hasRegenerationAttemptsForFirstDay { + constraints.regenerationAttemptsForFirstDay = generationConstraints.regenerationAttemptsForFirstDay + } + if generationConstraints.hasRegenerationAttemptsForConsecutiveDay { + constraints.regenerationAttemptsForConsecutiveDay = generationConstraints.regenerationAttemptsForConsecutiveDay + } + if generationConstraints.hasRegenerationAttemptsThreshold { + constraints.regenerationAttemptsThreshold = generationConstraints.regenerationAttemptsThreshold + } + } let runtime = RequestPayload.Runtime( - constraints: generationConstraints, + constraints: constraints, gameDays: gameDays, divisions: divisions, entries: entries, From 344d2334531cf3701a4677640c13738c19834a0c Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 15:08:58 -0500 Subject: [PATCH 31/33] 2 `MatchupBlock` fixes --- Sources/league-scheduling/data/MatchupBlock.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/league-scheduling/data/MatchupBlock.swift b/Sources/league-scheduling/data/MatchupBlock.swift index 1447519..a6f7ca4 100644 --- a/Sources/league-scheduling/data/MatchupBlock.swift +++ b/Sources/league-scheduling/data/MatchupBlock.swift @@ -247,8 +247,8 @@ extension LeagueScheduleData { // successfully assigned remainingPrioritizedEntries.removeMember(leagueMatchup.home) remainingPrioritizedEntries.removeMember(leagueMatchup.away) - selectedEntries.removeMember(leagueMatchup.home) - selectedEntries.removeMember(leagueMatchup.away) + selectedEntries.insertMember(leagueMatchup.home) + selectedEntries.insertMember(leagueMatchup.away) return leagueMatchup } private static func selectAndAssignMatchup( @@ -286,8 +286,8 @@ extension LeagueScheduleData { // successfully assigned remainingPrioritizedEntries.removeMember(leagueMatchup.home) remainingPrioritizedEntries.removeMember(leagueMatchup.away) - selectedEntries.removeMember(leagueMatchup.home) - selectedEntries.removeMember(leagueMatchup.away) + selectedEntries.insertMember(leagueMatchup.home) + selectedEntries.insertMember(leagueMatchup.away) return leagueMatchup } } \ No newline at end of file From 5bd63fa44bede8c08afbea536583e213ad9c8127 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Tue, 17 Mar 2026 20:49:45 -0500 Subject: [PATCH 32/33] logic fix when calculating bit set `availableMatchupPairs` --- Sources/league-scheduling/util/BitSet128.swift | 4 +--- Sources/league-scheduling/util/BitSet64.swift | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/Sources/league-scheduling/util/BitSet128.swift b/Sources/league-scheduling/util/BitSet128.swift index 440ea7a..b16eb5f 100644 --- a/Sources/league-scheduling/util/BitSet128.swift +++ b/Sources/league-scheduling/util/BitSet128.swift @@ -122,13 +122,11 @@ extension BitSet128: SetOfEntryIDs where Element == Entry.IDValue { maxSameOpponentMatchups: MaximumSameOpponentMatchups ) -> Set { var pairs = Set(minimumCapacity: (count-1) * 2) - var index = 0 forEach { home in let assignedHome = assignedEntryHomeAways[unchecked: home] let maxSameOpponentMatchups = maxSameOpponentMatchups[unchecked: home] - index += 1 forEach { away in - if away >= index, assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { + if away > home, assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { pairs.insert(.init(team1: home, team2: away)) } } diff --git a/Sources/league-scheduling/util/BitSet64.swift b/Sources/league-scheduling/util/BitSet64.swift index 3a30390..c3efe17 100644 --- a/Sources/league-scheduling/util/BitSet64.swift +++ b/Sources/league-scheduling/util/BitSet64.swift @@ -128,13 +128,11 @@ extension BitSet64: SetOfEntryIDs where Element == Entry.IDValue { maxSameOpponentMatchups: MaximumSameOpponentMatchups ) -> Set { var pairs = Set(minimumCapacity: (count-1) * 2) - var index = 0 forEach { home in let assignedHome = assignedEntryHomeAways[unchecked: home] let maxSameOpponentMatchups = maxSameOpponentMatchups[unchecked: home] - index += 1 forEach { away in - if away >= index, assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { + if away > home, assignedHome[unchecked: away].sum < maxSameOpponentMatchups[unchecked: away] { pairs.insert(.init(team1: home, team2: away)) } } From 2366e56e3606dfe8f056f51df11de1cfddbe2575 Mon Sep 17 00:00:00 2001 From: RandomHashTags Date: Wed, 18 Mar 2026 12:34:35 -0500 Subject: [PATCH 33/33] add, and incorporate `ScheduleConfiguration`, the new balance home/away logic --- .../data/BalanceHomeAway.swift | 120 ++++++++++++++++++ .../league-scheduling/data/Generation.swift | 1 + 2 files changed, 121 insertions(+) diff --git a/Sources/league-scheduling/data/BalanceHomeAway.swift b/Sources/league-scheduling/data/BalanceHomeAway.swift index 71b7c4f..54df542 100644 --- a/Sources/league-scheduling/data/BalanceHomeAway.swift +++ b/Sources/league-scheduling/data/BalanceHomeAway.swift @@ -1,4 +1,5 @@ +// MARK: Matchup pair extension MatchupPair { /// Balances home/away allocations, mutating `team1` (home) and `team2` (away) if necessary. #if SpecializeScheduleConfiguration @@ -42,4 +43,123 @@ extension MatchupPair { guard away1 == away2 else { return away1 < away2 } return Bool.random() } +} + +// MARK: LeagueScheduleData +extension LeagueScheduleData { + #if SpecializeScheduleConfiguration + @_specialize(where Config == ScheduleConfig, BitSet64, BitSet64, BitSet64>) + @_specialize(where Config == ScheduleConfig, Set, Set, Set>) + #endif + mutating func balanceHomeAway( + generationData: inout LeagueGenerationData + ) { + //return + #if LOG + print("BalanceHomeAway;LeagueScheduleData;before;home=\(assignmentState.homeMatchups);away=\(assignmentState.awayMatchups)") + #endif + + let now = clock.now + var unbalancedEntryIDs = Config.EntryIDSet() + unbalancedEntryIDs.reserveCapacity(entriesCount) + var neededFlipsToBalance = [(home: UInt8, away: UInt8)](repeating: (0, 0), count: entriesCount) + for entryID in 0.. balanceNumber { + neededFlipsToBalance[unchecked: entryID].home = home - balanceNumber + } else { + neededFlipsToBalance[unchecked: entryID].away = away - balanceNumber + } + } + guard !unbalancedEntryIDs.isEmpty else { + appendExecutionStep(now: now) + return + } + var flippable = Set() + for day in 0.. 0 { + flipped = flippable.filter({ + $0.matchup.home == entryID + && neededFlipsToBalance[unchecked: $0.matchup.home].home > 0 + && neededFlipsToBalance[unchecked: $0.matchup.away].away > 0 + }).randomElement() + } else { + flipped = flippable.filter({ + $0.matchup.away == entryID + && neededFlipsToBalance[unchecked: $0.matchup.home].home > 0 + && neededFlipsToBalance[unchecked: $0.matchup.away].away > 0 + }).randomElement() + } + if var flipped { + flippable.remove(flipped) + flipHomeAway(matchup: &flipped, neededFlipsToBalance: &neededFlipsToBalance, generationData: &generationData) + if neededFlipsToBalance[unchecked: flipped.matchup.home] == (0, 0) { + unbalancedEntryIDs.removeMember(flipped.matchup.home) + } + if neededFlipsToBalance[unchecked: flipped.matchup.away] == (0, 0) { + unbalancedEntryIDs.removeMember(flipped.matchup.away) + } + } else { + // TODO: improve? for now we can just skip it + unbalancedEntryIDs.removeMember(entryID) + } + } + + #if LOG + print("BalanceHomeAway;LeagueScheduleData;after;home=\(assignmentState.homeMatchups);away=\(assignmentState.awayMatchups)") + #endif + + appendExecutionStep(now: now) + } + private mutating func flipHomeAway( + matchup: inout FlippableMatchup, + neededFlipsToBalance: inout [(home: UInt8, away: UInt8)], + generationData: inout LeagueGenerationData + ) { + #if LOG + print("BalanceHomeAway;flipHomeAway;day=\(matchup.day);matchup=\(matchup.matchup.description);neededFlipsToBalance[home]=\(neededFlipsToBalance[unchecked: matchup.matchup.home]);neededFlipsToBalance[away]=\(neededFlipsToBalance[unchecked: matchup.matchup.away])") + #endif + + generationData.schedule[unchecked: matchup.day].remove(matchup.matchup) + let home = matchup.matchup.home + let away = matchup.matchup.away + neededFlipsToBalance[unchecked: home].home -= 1 + neededFlipsToBalance[unchecked: away].away -= 1 + + #if LOG + assignmentState.homeMatchups[unchecked: home] -= 1 + assignmentState.awayMatchups[unchecked: home] += 1 + assignmentState.homeMatchups[unchecked: away] += 1 + assignmentState.awayMatchups[unchecked: away] -= 1 + #endif + + matchup.matchup.home = away + matchup.matchup.away = home + generationData.schedule[unchecked: matchup.day].insert(matchup.matchup) + } + private struct FlippableMatchup: Hashable, Sendable { + let day:DayIndex + var matchup:Matchup + } + + private mutating func appendExecutionStep(now: ContinuousClock.Instant) { + let elapsed = clock.now - now + executionSteps.append(.init(key: "final balanceHomeAway", duration: elapsed)) + } } \ No newline at end of file diff --git a/Sources/league-scheduling/data/Generation.swift b/Sources/league-scheduling/data/Generation.swift index d637b17..540cb9b 100644 --- a/Sources/league-scheduling/data/Generation.swift +++ b/Sources/league-scheduling/data/Generation.swift @@ -282,6 +282,7 @@ extension RequestPayload.Runtime { gameDayRegenerationAttempt = 0 } } + data.balanceHomeAway(generationData: &generationData) finalizeGenerationData(generationData: &generationData, data: data) return generationData }