Vereinsmeisterschaften  22aa7800eae54b428d40e835886cefe1fdefdfdf
This is a software that can be used to manage the internal competition of the swimming club Illertissen called "Vereinsmeisterschaften".
Loading...
Searching...
No Matches
RaceService.cs
1using CommunityToolkit.Mvvm.ComponentModel;
2using System.Collections.ObjectModel;
7
9{
13 public class RaceService : ObservableObject, IRaceService
14 {
19
23 public event EventHandler OnFileFinished;
24
25 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
26
27 private IFileService _fileService;
28 private IPersonService _personService;
29 private ICompetitionService _competitionService;
30 private IWorkspaceService _workspaceService;
31
32 private int _nextVariantID;
33
40 public RaceService(IFileService fileService, IPersonService personService, ICompetitionService competitionService)
41 {
42 _fileService = fileService;
43 _personService = personService;
44 _personService.SetRaceServiceObj(this); // Dependency Injection can't be used in the constructor because of circular dependency
45 _competitionService = competitionService;
46
47 _nextVariantID = 1;
48 }
49
55 public void SetWorkspaceServiceObj(IWorkspaceService workspaceService)
56 {
57 _workspaceService = workspaceService;
58 }
59
60 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
61
65 public ObservableCollection<RacesVariant> AllRacesVariants { get; private set; } = new ObservableCollection<RacesVariant>();
66
70 public RacesVariant PersistedRacesVariant => AllRacesVariants.Where(r => r.IsPersistent).FirstOrDefault();
71
76
80 public string PersistentPath { get; set; }
81
82 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
83
91 public async Task<ObservableCollection<RacesVariant>> CalculateRacesVariants(CancellationToken cancellationToken, ProgressDelegate onProgressIteration = null, ProgressDelegate onProgressSolution = null)
92 {
93 // Collect all starts
94 _competitionService.UpdateAllCompetitionsForPerson();
95 List<PersonStart> starts = _personService.GetAllPersonStarts();
96
97 // Create groups of competitions with same style and distance
98 Dictionary<(SwimmingStyles, ushort), List<PersonStart>> groupedValuesStarts = new Dictionary<(SwimmingStyles, ushort), List<PersonStart>>();
99 for (int i = 0; i < starts.Count; i++)
100 {
101 if (!starts[i].IsCompetitionObjAssigned) { continue; }
102
103 (SwimmingStyles, ushort) key = (starts[i].CompetitionObj.SwimmingStyle, starts[i].CompetitionObj.Distance);
104 if (!groupedValuesStarts.ContainsKey(key))
105 {
106 groupedValuesStarts.Add(key, new List<PersonStart>());
107 }
108 else if (groupedValuesStarts[key] == null)
109 {
110 groupedValuesStarts[key] = new List<PersonStart>();
111 }
112 groupedValuesStarts[key].Add(starts[i]);
113 }
114
115 ushort numSwimLanes = _workspaceService?.Settings?.GetSettingValue<ushort>(WorkspaceSettings.GROUP_RACE_CALCULATION, WorkspaceSettings.SETTING_RACE_CALCULATION_NUMBER_OF_SWIM_LANES) ?? 0;
116 ushort numberRequestedVariants = _workspaceService?.Settings?.GetSettingValue<ushort>(WorkspaceSettings.GROUP_RACE_CALCULATION, WorkspaceSettings.SETTING_RACE_CALCULATION_NUM_RACE_VARIANTS_AFTER_CALCULATION) ?? 0;
117 int maxRaceVariantCalculationLoops = _workspaceService?.Settings?.GetSettingValue<int>(WorkspaceSettings.GROUP_RACE_CALCULATION, WorkspaceSettings.SETTING_RACE_CALCULATION_MAX_CALCULATION_LOOPS) ?? 0;
118 double minRacesVariantsScore = _workspaceService?.Settings?.GetSettingValue<double>(WorkspaceSettings.GROUP_RACE_CALCULATION, WorkspaceSettings.SETTING_RACE_CALCULATION_MIN_RACES_VARIANTS_SCORE) ?? 0;
119 int numberOfResultsToGenerate = Math.Max(0, numberRequestedVariants - AllRacesVariants.Count(r => r.KeepWhileRacesCalculation));
120
121 RacesVariantsGenerator generator = new RacesVariantsGenerator(new Progress<double>(progress => onProgressIteration?.Invoke(this, (float)progress, "")),
122 new Progress<double>(progress => onProgressSolution?.Invoke(this, (float)progress, "")),
123 numberOfResultsToGenerate,
124 maxRaceVariantCalculationLoops,
125 minRacesVariantsScore,
126 numSwimLanes);
127 List<RacesVariant> tmpRacesVariants = await generator.GenerateBestRacesAsync(groupedValuesStarts.Values.ToList(), _workspaceService, cancellationToken);
128
129 if (!cancellationToken.IsCancellationRequested)
130 {
131 List<RacesVariant> racesToDelete = AllRacesVariants.Where(r => !r.KeepWhileRacesCalculation).ToList();
132 racesToDelete.ForEach(r => AllRacesVariants.Remove(r));
133 tmpRacesVariants.ForEach(AddRacesVariant);
135 OnPropertyChanged(nameof(PersistedRacesVariant));
136 }
137 return AllRacesVariants;
138 }
139
140 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
141
150 public async Task<bool> Load(string path, CancellationToken cancellationToken)
151 {
152 bool importingResult = false;
153 Exception exception = null;
154
155 SynchronizationContext uiContext = SynchronizationContext.Current;
156 await Task.Run(() =>
157 {
158 try
159 {
160 if (!File.Exists(path))
161 {
162 OnFileProgress?.Invoke(this, 0);
163 uiContext.Send((d) => { ClearAllRacesVariants(); }, null);
164 OnFileProgress?.Invoke(this, 100);
165 }
166 else
167 {
168 List<Race> raceList = _fileService.LoadFromCsv<Race>(path, cancellationToken, setRacePropertyFromString, OnFileProgress, (header) =>
169 {
170 return PropertyNameLocalizedStringHelper.FindProperty(typeof(Race), header);
171 });
172 RacesVariant bestRacesVariant = new RacesVariant(raceList, _workspaceService);
173 bestRacesVariant.IsPersistent = true;
174 uiContext.Send((d) =>
175 {
177 AddRacesVariant(bestRacesVariant);
178 }, null);
179 }
180
182 else { _persistedRacesVariantOnLoad = new RacesVariant(PersistedRacesVariant, true, true, _workspaceService); }
183
184 PersistentPath = path;
185 importingResult = true;
186 }
187 catch (OperationCanceledException)
188 {
189 importingResult = false;
190 }
191 catch (Exception ex)
192 {
193 exception = ex;
194 }
195 });
196 OnFileFinished?.Invoke(this, null);
197 if (exception != null) { throw exception; }
198 return importingResult;
199 }
200
201 private int _lastParsedDistance = 0;
202 private SwimmingStyles _lastParsedStyle = SwimmingStyles.Unknown;
209 private void setRacePropertyFromString(Race dataObj, string propertyName, string value)
210 {
211 if (string.IsNullOrEmpty(value)) return;
212
213 switch (propertyName)
214 {
215 case nameof(Race.Distance): _lastParsedDistance = int.Parse(value); break;
216 case nameof(Race.Style): if (EnumCoreLocalizedStringHelper.TryParse(value, out SwimmingStyles parsedStyle)) { _lastParsedStyle = parsedStyle; } break;
217 default:
218 {
219 if (dataObj.Starts == null) { dataObj.Starts = new ObservableCollection<PersonStart>(); }
220 if(_lastParsedDistance != 0 && _lastParsedStyle != SwimmingStyles.Unknown)
221 {
222 string[] nameParts = value.Split(',');
223 string firstName = nameParts.Length > 0 ? nameParts[0].Trim() : "";
224 string name = nameParts.Length > 1 ? nameParts[1].Trim() : "";
225 PersonStart matchingStart = _personService.GetAllPersonStarts().Where(s => s.Style == _lastParsedStyle &&
226 s.CompetitionObj?.Distance == _lastParsedDistance &&
227 s.PersonObj?.FirstName == firstName &&
228 s.PersonObj?.Name == name).FirstOrDefault();
229 if (matchingStart != null)
230 {
231 dataObj.Starts.Add(matchingStart);
232 }
233 }
234 break;
235 }
236 }
237 }
238
239 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
240
247 public async Task<bool> Save(CancellationToken cancellationToken, string path = "")
248 {
249 if (string.IsNullOrEmpty(path)) { path = PersistentPath; }
250
251 bool saveResult = false;
252 Exception exception = null;
253 await Task.Run(() =>
254 {
255 try
256 {
257 if (PersistedRacesVariant == null)
258 {
259 if (File.Exists(path)) { File.Delete(path); }
261 }
262 else
263 {
264 int maxNumberStarts = PersistedRacesVariant.Races.Count == 0 ? 0 : PersistedRacesVariant.Races.Select(r => r.Starts.Count).Max();
265 _fileService.SaveToCsv(path, PersistedRacesVariant.Races.ToList(), cancellationToken, OnFileProgress,
266 (data, parentObject, currentProperty) =>
267 {
268 if (data is IList<PersonStart> dataList)
269 {
270 return string.Join(";", dataList.Select(p => p.PersonObj?.FirstName + ", " + p.PersonObj?.Name));
271 }
272 else if (data is Enum dataEnum)
273 {
274 return EnumCoreLocalizedStringHelper.Convert(dataEnum);
275 }
276 else
277 {
278 return data.ToString();
279 }
280 },
281 (header, headerType) =>
282 {
283 if (headerType == typeof(ObservableCollection<PersonStart>))
284 {
285 string formatedHeader = "";
286 for(int i = 1; i <= maxNumberStarts; i++)
287 {
288 formatedHeader += $"{PropertyNameLocalizedStringHelper.Convert(typeof(Race), "Person")} {i};";
289 }
290 formatedHeader = formatedHeader.Trim(';');
291 return formatedHeader;
292 }
293 else
294 {
295 return PropertyNameLocalizedStringHelper.Convert(typeof(Race), header);
296 }
297 });
298
299 _persistedRacesVariantOnLoad = new RacesVariant(PersistedRacesVariant, true, true, _workspaceService);
300 }
301 saveResult = true;
302 }
303 catch (OperationCanceledException)
304 {
305 saveResult = false;
306 }
307 catch (Exception ex)
308 {
309 exception = ex;
310 }
311 });
312 OnFileFinished?.Invoke(this, null);
313 if (exception != null) { throw exception; }
314 return saveResult;
315 }
316
317 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
318
323 public void AddRacesVariant(RacesVariant racesVariant)
324 {
325 AllRacesVariants.Add(racesVariant);
326 racesVariant.VariantID = _nextVariantID;
327 _nextVariantID++;
328 racesVariant.PropertyChanged += RacesVariant_PropertyChanged;
329 OnPropertyChanged(nameof(HasUnsavedChanges));
330 }
331
336 public void RemoveRacesVariant(RacesVariant racesVariant)
337 {
338 if(racesVariant == null) { return; }
339 racesVariant.PropertyChanged -= RacesVariant_PropertyChanged;
340 AllRacesVariants.Remove(racesVariant);
341 OnPropertyChanged(nameof(HasUnsavedChanges));
342 }
343
348 {
349 foreach(RacesVariant racesVariant in AllRacesVariants)
350 {
351 racesVariant.PropertyChanged -= RacesVariant_PropertyChanged;
352 AllRacesVariants.Remove(racesVariant);
353 }
354 OnPropertyChanged(nameof(HasUnsavedChanges));
355 _nextVariantID = 1;
356 }
357
361 public void ResetToLoadedState()
362 {
363 int persistedRaceIndex = PersistedRacesVariant == null ? -1 : AllRacesVariants.IndexOf(PersistedRacesVariant);
364 RacesVariant resetRaceVariant = new RacesVariant(_persistedRacesVariantOnLoad, true, false, _workspaceService) { IsPersistent = true };
365 if (persistedRaceIndex != -1)
366 {
367 PersistedRacesVariant.PropertyChanged -= RacesVariant_PropertyChanged;
368 AllRacesVariants[persistedRaceIndex] = resetRaceVariant;
369 resetRaceVariant.PropertyChanged += RacesVariant_PropertyChanged;
370 }
371 else
372 {
373 AddRacesVariant(resetRaceVariant);
374 }
376
377 OnPropertyChanged(nameof(AllRacesVariants));
378 OnPropertyChanged(nameof(PersistedRacesVariant));
379 OnPropertyChanged(nameof(HasUnsavedChanges));
380 }
381
387 {
388 List<PersonStart> allStarts = _personService.GetAllPersonStarts();
389 foreach (RacesVariant racesVariant in AllRacesVariants)
390 {
391 foreach (Race race in racesVariant.Races)
392 {
393 ObservableCollection<PersonStart> newRaceStarts = new ObservableCollection<PersonStart>();
394 foreach (PersonStart oldStart in race.Starts)
395 {
396 PersonStart newStart = allStarts.Where(s => s.PersonObj.Equals(oldStart.PersonObj) &&
397 s.Style == oldStart.Style).FirstOrDefault();
398 newRaceStarts.Add(newStart ?? oldStart);
399 }
400 race.Starts = newRaceStarts;
401 }
403 }
404 }
405
410 {
411 AllRacesVariants = new ObservableCollection<RacesVariant>(AllRacesVariants.OrderByDescending(r => r.Score));
412 OnPropertyChanged(nameof(AllRacesVariants));
413 }
414
420 public int RecalculateVariantIDs(int oldVariantID = -1)
421 {
422 int newVariantID = -1;
423 _nextVariantID = 1;
424 foreach (RacesVariant racesVariant in AllRacesVariants)
425 {
426 if (racesVariant.VariantID == oldVariantID) { newVariantID = _nextVariantID; }
427 racesVariant.VariantID = _nextVariantID;
428 _nextVariantID++;
429
430 racesVariant.UpdateNotAssignedStarts(_personService.GetAllPersonStarts());
431 }
432 OnPropertyChanged(nameof(AllRacesVariants));
433 return newVariantID;
434 }
435
436 private void RacesVariant_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
437 {
438 if (e.PropertyName == nameof(RacesVariant.Races))
439 {
440 (sender as RacesVariant)?.UpdateNotAssignedStarts(_personService.GetAllPersonStarts());
441 }
442 OnPropertyChanged(nameof(HasUnsavedChanges));
443 OnPropertyChanged(nameof(AllRacesVariants));
444 OnPropertyChanged(nameof(PersistedRacesVariant));
445 }
446
447 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
448
454 public void CleanupRacesVariants(bool removeInactiveStarts = true)
455 {
456 List<PersonStart> validPersonStarts = _personService.GetAllPersonStarts();
457
458 foreach (RacesVariant racesVariant in AllRacesVariants)
459 {
460 foreach(Race race in racesVariant.Races)
461 {
462 // Find all starts in this race that are no longer part of the valid PersonStarts and remove them from the Race
463 List<PersonStart> startsToDelete = race.Starts.Except(validPersonStarts, new PersonStartWithoutIsActiveEqualityComparer()).ToList();
464 if (removeInactiveStarts)
465 {
466 // Also remove all inactive starts
467 startsToDelete.AddRange(race.Starts.Where(s => !s.IsActive));
468 // Make sure to remove duplicate elements
469 startsToDelete = startsToDelete.Distinct(new PersonStartWithoutIsActiveEqualityComparer()).ToList();
470 }
471 // Remove the starts from the race
472 startsToDelete.ForEach(s => race.Starts.Remove(s));
473 }
474
475 // Find all empty races and remove them from the variant
476 List<Race> racesToDelete = racesVariant.Races.Where(r => r.Starts.Count == 0).ToList();
477 racesToDelete.ForEach(r => racesVariant.Races.Remove(r));
478 }
479 }
480
481 // ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
482
488 {
489 get
490 {
492 {
494 return !fullEqualityComparer.Equals(PersistedRacesVariant, _persistedRacesVariantOnLoad);
495 }
496 else if(PersistedRacesVariant == null && _persistedRacesVariantOnLoad == null)
497 {
498 return false;
499 }
500 else
501 {
502 return true;
503 }
504 }
505 }
506 }
507}
Class describing a person.
Definition Person.cs:12
Class describing a start of a person.
Definition PersonStart.cs:9
Comparer that compares a PersonStart without regarding the PersonStart.IsActive
Class that represents a single race.
Definition Race.cs:12
int Distance
Distance for this Race.
Definition Race.cs:82
ObservableCollection< PersonStart > Starts
List with all starts of this Race
Definition Race.cs:90
SwimmingStyles Style
for this .
Definition Race.cs:76
Comparer that only uses most properties of a RacesVariant to determine equality:
bool Equals(RacesVariant x, RacesVariant y)
Check if two RacesVariant objects are equal based on basic properties.
Class that represents a combination variant of all single races.
void UpdateNotAssignedStarts(List< PersonStart > allStarts)
Update the list of not assigned PersonStart objects.
ObservableCollection< Race > Races
List with races.
int VariantID
Number for this RacesVariant
void UpdateRaceStartsCollectionChangedEvent()
Update the internal event handlers for the Race.Starts collection changed events.
async Task< bool > Save(CancellationToken cancellationToken, string path="")
Save the PersistedRacesVariant to a file.
RaceService(IFileService fileService, IPersonService personService, ICompetitionService competitionService)
Constructor.
RacesVariant _persistedRacesVariantOnLoad
RacesVariant object that is marked as best result at the time the Load(string, CancellationToken) met...
async Task< bool > Load(string path, CancellationToken cancellationToken)
Load the best race as the only element to the AllRacesVariants.
bool HasUnsavedChanges
Check if the PersistedRacesVariant has not saved changed.
void AddRacesVariant(RacesVariant racesVariant)
Add a new RacesVariant to the list AllRacesVariants
void CleanupRacesVariants(bool removeInactiveStarts=true)
Remove non-existing and inactive PersonStart objects from all races in AllRacesVariants.
void SetWorkspaceServiceObj(IWorkspaceService workspaceService)
Save the reference to the IWorkspaceService object.
void ClearAllRacesVariants()
Remove all RacesVariant from the list AllRacesVariants
async Task< ObservableCollection< RacesVariant > > CalculateRacesVariants(CancellationToken cancellationToken, ProgressDelegate onProgressIteration=null, ProgressDelegate onProgressSolution=null)
Calculate some RacesVariant objects for all person starts.
void ReassignAllPersonStarts()
Reassign all PersonStart objects in all RacesVariant in AllRacesVariants.
int RecalculateVariantIDs(int oldVariantID=-1)
Reassign all RacesVariant.VariantID so that the IDs start from 1 and have no gaps.
void ResetToLoadedState()
Reset the to the state when the method was called.
void RemoveRacesVariant(RacesVariant racesVariant)
Remove the given RacesVariant object from the list AllRacesVariants
EventHandler OnFileFinished
Event that is raised when the file operation is finished.
ObservableCollection< RacesVariant > AllRacesVariants
List with all RacesVariant (including the loaded and calculated ones)
RacesVariant PersistedRacesVariant
RacesVariant object that is persisted (saved/loaded to/from a file).
ProgressDelegate OnFileProgress
Event that is raised when the file operation progress changes.
void SortVariantsByScore()
Sort the complete list AllRacesVariants descending by the RacesVariant.Score
void setRacePropertyFromString(Race dataObj, string propertyName, string value)
Set the requested property in the Race object by parsing the given string value.
string PersistentPath
Path to the race file.
Class to generate different variants of RacesVariant using the score calculation.
async Task< List< RacesVariant > > GenerateBestRacesAsync(List< List< PersonStart > > sets, IWorkspaceService workspaceService=null, CancellationToken cancellationToken=default)
Calculate the variants asynchronously.
Interface for a service used to get and store a list of objects.
Interface for a service that handles file operations.
Interface for a service used to get and store a list of Person objects.
List< PersonStart > GetAllPersonStarts(PersonStartFilters filter=PersonStartFilters.None, object filterParameter=null, bool onlyValidStarts=false)
Get all PersonStart objects for all Person objects that are not null.
void SetRaceServiceObj(IRaceService raceService)
Save the reference to the IRaceService object.
Interface for a service used to manage Race and RacesVariant objects.
Interface for a service used to manage a workspace.
delegate void ProgressDelegate(object sender, float progress, string currentStep="")
Delegate void for progress changes.
SwimmingStyles
Available swimming styles.