From dd4501b95cc37d8d74b25f105ae1b0a17eebfedf Mon Sep 17 00:00:00 2001 From: Raz Date: Fri, 27 Sep 2024 19:48:01 +0200 Subject: [PATCH] fix issue with matchscheduler disable any locks for creatiing a player manually --- PadelClub.xcodeproj/project.pbxproj | 2 + PadelClub/Data/Match.swift | 6 +- PadelClub/Data/MatchScheduler.swift | 305 ++++++++++-------- .../Player/Components/PlayerPopoverView.swift | 2 +- PadelClub/Views/Round/RoundView.swift | 2 +- 5 files changed, 181 insertions(+), 136 deletions(-) diff --git a/PadelClub.xcodeproj/project.pbxproj b/PadelClub.xcodeproj/project.pbxproj index a999dff..a2f8edc 100644 --- a/PadelClub.xcodeproj/project.pbxproj +++ b/PadelClub.xcodeproj/project.pbxproj @@ -3139,6 +3139,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; + GCC_OPTIMIZATION_LEVEL = 0; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; @@ -3182,6 +3183,7 @@ DEFINES_MODULE = YES; DEVELOPMENT_ASSET_PATHS = "\"PadelClub/Preview Content\""; DEVELOPMENT_TEAM = BQ3Y44M3Q6; + GCC_OPTIMIZATION_LEVEL = 0; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = PadelClub/Info.plist; INFOPLIST_KEY_CFBundleDisplayName = "Padel Club"; diff --git a/PadelClub/Data/Match.swift b/PadelClub/Data/Match.swift index 762f886..5476029 100644 --- a/PadelClub/Data/Match.swift +++ b/PadelClub/Data/Match.swift @@ -408,7 +408,7 @@ defer { } func next() -> Match? { - let matches: [Match] = self.tournamentStore.matches.filter { $0.round == round && $0.index > index } + let matches: [Match] = self.tournamentStore.matches.filter { $0.round == round && $0.index > index && $0.disabled == false } return matches.sorted(by: \.index).first } @@ -435,6 +435,10 @@ defer { else { return nil } } + func roundAndMatchTitle() -> String { + [roundTitle(), matchTitle()].compactMap({ $0 }).joined(separator: " ") + } + func topPreviousRoundMatchIndex() -> Int { return index * 2 + 1 } diff --git a/PadelClub/Data/MatchScheduler.swift b/PadelClub/Data/MatchScheduler.swift index 69d1011..4264da2 100644 --- a/PadelClub/Data/MatchScheduler.swift +++ b/PadelClub/Data/MatchScheduler.swift @@ -179,30 +179,39 @@ final class MatchScheduler : ModelObject, Storable { // Get the maximum count of matches in any group let maxMatchesCount = _groupStages.map { $0._matches().count }.max() ?? 0 - // Use zip and flatMap to flatten matches in the desired order + // Flatten matches in a round-robin order by cycling through each group let flattenedMatches = (0.. 0 { @@ -216,29 +225,42 @@ final class MatchScheduler : ModelObject, Storable { } (0.. Int { if loserBracket { @@ -271,70 +301,96 @@ final class MatchScheduler : ModelObject, Storable { } func roundMatchCanBePlayed(_ match: Match, roundObject: Round, slots: [TimeMatch], rotationIndex: Int, targetedStartDate: Date, minimumTargetedEndDate: inout Date) -> Bool { - print(roundObject.roundTitle(), match.matchTitle()) - + print("Evaluating match: \(match.roundAndMatchTitle()) in round: \(roundObject.roundTitle()) with index: \(match.index)") + if let roundStartDate = roundObject.startDate, targetedStartDate < roundStartDate { - print("can't start \(targetedStartDate) earlier than \(roundStartDate)") + print("Cannot start at \(targetedStartDate), earlier than round start date \(roundStartDate)") if targetedStartDate == minimumTargetedEndDate { + print("Updating minimumTargetedEndDate to roundStartDate: \(roundStartDate)") minimumTargetedEndDate = roundStartDate } else { + print("Setting minimumTargetedEndDate to the earlier of \(roundStartDate) and \(minimumTargetedEndDate)") minimumTargetedEndDate = min(roundStartDate, minimumTargetedEndDate) } + print("Returning false: Match cannot start earlier than the round start date.") return false } let previousMatches = roundObject.precedentMatches(ofMatch: match) - if previousMatches.isEmpty { return true } + if previousMatches.isEmpty { + print("No ancestors matches for this match, returning true. (eg beginning of tournament 1st bracket") + return true + } - let previousMatchSlots = slots.filter({ slot in - previousMatches.map { $0.id }.contains(slot.matchID) - }) + let previousMatchSlots = slots.filter { previousMatches.map { $0.id }.contains($0.matchID) } if previousMatchSlots.isEmpty { - if previousMatches.filter({ $0.disabled == false }).allSatisfy({ $0.startDate != nil }) { + if previousMatches.filter({ !$0.disabled }).allSatisfy({ $0.startDate != nil }) { + print("All previous matches have start dates, returning true.") return true } + print("Some previous matches are pending, returning false.") return false } - if previousMatches.filter({ $0.disabled == false }).count > previousMatchSlots.count { - if previousMatches.filter({ $0.disabled == false }).anySatisfy({ $0.startDate != nil }) { + if previousMatches.filter({ !$0.disabled }).count > previousMatchSlots.count { + if previousMatches.filter({ !$0.disabled }).anySatisfy({ $0.startDate != nil }) { + print("Some previous matches started, returning true.") return true } + print("Not enough previous matches have started, returning false.") return false } - + var includeBreakTime = false - if accountLoserBracketBreakTime && roundObject.isLoserBracket() { includeBreakTime = true + print("Including break time for loser bracket.") } - - if accountUpperBracketBreakTime && roundObject.isLoserBracket() == false { + + if accountUpperBracketBreakTime && !roundObject.isLoserBracket() { includeBreakTime = true + print("Including break time for upper bracket.") } - let previousMatchIsInPreviousRotation = previousMatchSlots.allSatisfy({ $0.rotationIndex + rotationDifference(loserBracket: roundObject.isLoserBracket()) < rotationIndex }) - - guard let minimumPossibleEndDate = previousMatchSlots.map({ $0.estimatedEndDate(includeBreakTime: includeBreakTime) }).max() else { + let previousMatchIsInPreviousRotation = previousMatchSlots.allSatisfy { + $0.rotationIndex + rotationDifference(loserBracket: roundObject.isLoserBracket()) < rotationIndex + } + + if previousMatchIsInPreviousRotation { + print("All previous matches are from earlier rotations, returning true.") + } else { + print("Some previous matches are from the current rotation.") + } + + guard let minimumPossibleEndDate = previousMatchSlots.map({ + $0.estimatedEndDate(includeBreakTime: includeBreakTime) + }).max() else { + print("No valid previous match end date, returning \(previousMatchIsInPreviousRotation).") return previousMatchIsInPreviousRotation } if targetedStartDate >= minimumPossibleEndDate { if rotationDifferenceIsImportant { + print("Targeted start date is after the minimum possible end date and rotation difference is important, returning \(previousMatchIsInPreviousRotation).") return previousMatchIsInPreviousRotation } else { + print("Targeted start date is after the minimum possible end date, returning true.") return true } } else { if targetedStartDate == minimumTargetedEndDate { + print("Updating minimumTargetedEndDate to minimumPossibleEndDate: \(minimumPossibleEndDate)") minimumTargetedEndDate = minimumPossibleEndDate } else { + print("Setting minimumTargetedEndDate to the earlier of \(minimumPossibleEndDate) and \(minimumTargetedEndDate)") minimumTargetedEndDate = min(minimumPossibleEndDate, minimumTargetedEndDate) } + print("Targeted start date is before the minimum possible end date, returning false.") return false } } + func getNextStartDate(fromPreviousRotationSlots slots: [TimeMatch], includeBreakTime: Bool) -> Date? { slots.map { $0.estimatedEndDate(includeBreakTime: includeBreakTime) }.min() @@ -370,13 +426,15 @@ final class MatchScheduler : ModelObject, Storable { } func roundDispatcher(numberOfCourtsAvailablePerRotation: Int, flattenedMatches: [Match], dispatcherStartDate: Date, initialCourts: [Int]?) -> MatchDispatcher { - var slots = [TimeMatch]() var _startDate: Date? var rotationIndex = 0 var availableMatchs = flattenedMatches.filter({ $0.startDate == nil }) let courtsUnavailability = courtsUnavailability var issueFound: Bool = false + + // Log start of the function + print("Starting roundDispatcher with \(availableMatchs.count) matches and \(numberOfCourtsAvailablePerRotation) courts available") flattenedMatches.filter { $0.startDate != nil }.sorted(by: \.startDate!).forEach { match in if _startDate == nil { @@ -389,24 +447,21 @@ final class MatchScheduler : ModelObject, Storable { let timeMatch = TimeMatch(matchID: match.id, rotationIndex: rotationIndex, courtIndex: match.courtIndex ?? 0, startDate: match.startDate!, durationLeft: match.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: match.matchFormat.breakTime.breakTime) slots.append(timeMatch) } - - if slots.isEmpty == false { + + if !slots.isEmpty { rotationIndex += 1 } var freeCourtPerRotation = [Int: [Int]]() - let availableCourt = numberOfCourtsAvailablePerRotation - var courts = initialCourts ?? (0.. 0 - while availableMatchs.count > 0 && issueFound == false { + while !availableMatchs.isEmpty && !issueFound && rotationIndex < 100 { freeCourtPerRotation[rotationIndex] = [] let previousRotationSlots = slots.filter({ $0.rotationIndex == rotationIndex - 1 }) var rotationStartDate: Date = getNextStartDate(fromPreviousRotationSlots: previousRotationSlots, includeBreakTime: false) ?? dispatcherStartDate - + if shouldStartAtDispatcherDate { rotationStartDate = dispatcherStartDate shouldStartAtDispatcherDate = false @@ -414,23 +469,28 @@ final class MatchScheduler : ModelObject, Storable { courts = rotationIndex == 0 ? courts : (0.. 0, let freeCourtPreviousRotation = freeCourtPerRotation[rotationIndex - 1], freeCourtPreviousRotation.count > 0 { - print("scenario where we are waiting for a breaktime to be over without any match to play in between or a free court was available and we need to recheck breaktime left on it") - let previousPreviousRotationSlots = slots.filter({ $0.rotationIndex == rotationIndex - 2 && freeCourtPreviousRotation.contains($0.courtIndex) }) + // Log courts availability and start date + print("Courts available at rotation \(rotationIndex): \(courts)") + print("Rotation start date: \(rotationStartDate)") + + // Check for court availability and break time conflicts + if rotationIndex > 0, let freeCourtPreviousRotation = freeCourtPerRotation[rotationIndex - 1], !freeCourtPreviousRotation.isEmpty { + print("Handling break time conflicts or waiting for free courts") + let previousPreviousRotationSlots = slots.filter { $0.rotationIndex == rotationIndex - 2 && freeCourtPreviousRotation.contains($0.courtIndex) } let previousEndDate = getNextStartDate(fromPreviousRotationSlots: previousPreviousRotationSlots, includeBreakTime: accountUpperBracketBreakTime) let previousEndDateNoBreak = getNextStartDate(fromPreviousRotationSlots: previousPreviousRotationSlots, includeBreakTime: false) - let noBreakAlreadyTested = previousRotationSlots.anySatisfy({ $0.startDate == previousEndDateNoBreak }) + let noBreakAlreadyTested = previousRotationSlots.anySatisfy { $0.startDate == previousEndDateNoBreak } + if let previousEndDate, let previousEndDateNoBreak { let differenceWithBreak = rotationStartDate.timeIntervalSince(previousEndDate) let differenceWithoutBreak = rotationStartDate.timeIntervalSince(previousEndDateNoBreak) - print("difference w break", differenceWithBreak) - print("difference w/o break", differenceWithoutBreak) + print("Difference with break: \(differenceWithBreak), without break: \(differenceWithoutBreak)") + let timeDifferenceLimitInSeconds = Double(timeDifferenceLimit * 60) var difference = differenceWithBreak + if differenceWithBreak <= 0 { difference = differenceWithoutBreak } else if differenceWithBreak > timeDifferenceLimitInSeconds && differenceWithoutBreak > timeDifferenceLimitInSeconds { @@ -438,148 +498,127 @@ final class MatchScheduler : ModelObject, Storable { } if difference > timeDifferenceLimitInSeconds && rotationStartDate.addingTimeInterval(-difference) != previousEndDate { - courts.removeAll(where: { index in freeCourtPreviousRotation.contains(index) - }) + courts.removeAll(where: { freeCourtPreviousRotation.contains($0) }) freeCourtPerRotation[rotationIndex] = courts courts = freeCourtPreviousRotation rotationStartDate = rotationStartDate.addingTimeInterval(-difference) } } - } else if let first = availableMatchs.first { - let duration = first.matchFormat.getEstimatedDuration(additionalEstimationDuration) + } else if let firstMatch = availableMatchs.first { + let duration = firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration) let courtsUnavailable = courtsUnavailable(startDate: rotationStartDate, duration: duration, courtsUnavailability: courtsUnavailability) if courtsUnavailable.count == numberOfCourtsAvailablePerRotation { - print("issue") + print("Issue: All courts unavailable in this rotation") issueFound = true } else { courts = Array(Set(courts).subtracting(Set(courtsUnavailable))) } } + // Dispatch courts and schedule matches dispatchCourts(availableCourts: numberOfCourtsAvailablePerRotation, courts: courts, availableMatchs: &availableMatchs, slots: &slots, rotationIndex: rotationIndex, rotationStartDate: rotationStartDate, freeCourtPerRotation: &freeCourtPerRotation, courtsUnavailability: courtsUnavailability) rotationIndex += 1 } - + + // Organize matches in slots var organizedSlots = [TimeMatch]() for i in 0.. courts.count { - print("roundMatchesCount \(roundMatchesCount) > \(courts.count)") - if currentRotationSameRoundMatches >= min(roundMatchesCount / 2, courts.count) { - print("return false, \(currentRotationSameRoundMatches) >= \(min(roundMatchesCount / 2, courts.count))") - return false - } - } - } - //if all is ok, we do a final check to see if the first - let indexInRound = match.indexInRound() - - print("Upper Round, index > 0, first Match of round \(indexInRound) and more than one court available; looking for next match (same round) \(indexInRound + 1)") - if roundObject.parent == nil && roundObject.index > 0, indexInRound == 0, let nextMatch = match.next() { - guard courtPosition < courts.count - 1, courts.count > 1 else { - print("next match and this match can not be played at the same time, returning false") + if shouldHandleUpperRoundSlice { + if roundObject.parent == nil && roundMatchesCount > courts.count && currentRotationSameRoundMatches >= min(roundMatchesCount / 2, courts.count) { + print("Returning false: Too many matches already played in the current rotation for round \(roundObject.roundTitle()).") return false } - if canBePlayed && roundMatchCanBePlayed(nextMatch, roundObject: roundObject, slots: slots, rotationIndex: rotationIndex, targetedStartDate: rotationStartDate, minimumTargetedEndDate: &minimumTargetedEndDate) { - - print("next match and this match can be played, returning true") - - return true - } } - - //not adding a last match of a 4-match round (final not included obviously) - print("\(currentRotationSameRoundMatches) modulo \(currentRotationSameRoundMatches%2) same round match is even, index of round is not 0 and upper bracket. If it's not the last court available \(courtIndex) == \(courts.count - 1)") + let indexInRound = match.indexInRound() - if shouldTryToFillUpCourtsAvailable == false { - if roundMatchesCount <= 4 && currentRotationSameRoundMatches%2 == 0 && roundObject.index != 0 && roundObject.parent == nil && ((courts.count > 1 && courtPosition >= courts.count - 1) || courts.count == 1 && availableCourts > 1) { - print("we return false") + if roundObject.parent == nil && roundObject.index > 0 && indexInRound == 0, let nextMatch = match.next() { + if courtPosition < courts.count - 1 && canBePlayed && roundMatchCanBePlayed(nextMatch, roundObject: roundObject, slots: slots, rotationIndex: rotationIndex, targetedStartDate: rotationStartDate, minimumTargetedEndDate: &minimumTargetedEndDate) { + print("Returning true: Both current \(match.index) and next match \(nextMatch.index) can be played in rotation \(rotationIndex).") + return true + } else { + print("Returning false: Either current match or next match cannot be played in rotation \(rotationIndex).") return false } } - + print("Returning true: Match \(match.roundAndMatchTitle()) can be played on court \(courtIndex).") return canBePlayed }) { - print(first.roundObject!.roundTitle(), first.matchTitle(), courtIndex, rotationStartDate) + print("Found match: \(firstMatch.roundAndMatchTitle()) for court \(courtIndex) at \(rotationStartDate)") + + matchPerRound[firstMatch.roundObject!.id, default: 0] += 1 + + let timeMatch = TimeMatch( + matchID: firstMatch.id, + rotationIndex: rotationIndex, + courtIndex: courtIndex, + startDate: rotationStartDate, + durationLeft: firstMatch.matchFormat.getEstimatedDuration(additionalEstimationDuration), + minimumBreakTime: firstMatch.matchFormat.breakTime.breakTime + ) - if first.roundObject!.parent == nil { - if let roundIndex = matchPerRound[first.roundObject!.id] { - matchPerRound[first.roundObject!.id] = roundIndex + 1 - } else { - matchPerRound[first.roundObject!.id] = 1 - } - } - let timeMatch = TimeMatch(matchID: first.id, rotationIndex: rotationIndex, courtIndex: courtIndex, startDate: rotationStartDate, durationLeft: first.matchFormat.getEstimatedDuration(additionalEstimationDuration), minimumBreakTime: first.matchFormat.breakTime.breakTime) slots.append(timeMatch) - availableMatchs.removeAll(where: { $0.id == first.id }) + availableMatchs.removeAll(where: { $0.id == firstMatch.id }) } else { - freeCourtPerRotation[rotationIndex]!.append(courtIndex) + print("No suitable match found for court \(courtIndex) in rotation \(rotationIndex). Adding court to freeCourtPerRotation.") + freeCourtPerRotation[rotationIndex]?.append(courtIndex) } + } - - if freeCourtPerRotation[rotationIndex]!.count == availableCourts { - print("no match found to be put in this rotation, check if we can put anything to another date") - freeCourtPerRotation[rotationIndex] = [] - let courtsUsed = getNextEarliestAvailableDate(from: slots) - var freeCourts: [Int] = [] - if courtsUsed.isEmpty { - freeCourts = (0.. Bool { let upperRounds: [Round] = tournament.rounds() diff --git a/PadelClub/Views/Player/Components/PlayerPopoverView.swift b/PadelClub/Views/Player/Components/PlayerPopoverView.swift index f1dbe6e..3e8ddcf 100644 --- a/PadelClub/Views/Player/Components/PlayerPopoverView.swift +++ b/PadelClub/Views/Player/Components/PlayerPopoverView.swift @@ -31,7 +31,7 @@ struct PlayerPopoverView: View { @State private var source: String? - init(source: String?, sex: Int, requiredField: [PlayerCreationField] = [.firstName, .lastName], creationCompletionHandler: @escaping (PlayerRegistration) -> Void) { + init(source: String?, sex: Int, requiredField: [PlayerCreationField] = [], creationCompletionHandler: @escaping (PlayerRegistration) -> Void) { if let source { let words = source.components(separatedBy: .whitespaces) if words.isEmpty == false { diff --git a/PadelClub/Views/Round/RoundView.swift b/PadelClub/Views/Round/RoundView.swift index 12bda95..37aa4c5 100644 --- a/PadelClub/Views/Round/RoundView.swift +++ b/PadelClub/Views/Round/RoundView.swift @@ -259,7 +259,7 @@ struct RoundView: View { #if DEBUG Spacer() - Text(match.teamScores.count.formatted()) + Text(match.index.formatted() + " " + match.teamScores.count.formatted()) #endif } } footer: {