private void LoadingThread(object obj) { try { IPreviewBeatmapLevel[] levels = ((Tuple <IPreviewBeatmapLevel[], Action <BeatmapDetails[]> >)obj).Item1; Action <BeatmapDetails[]> onFinish = ((Tuple <IPreviewBeatmapLevel[], Action <BeatmapDetails[]> >)obj).Item2; List <OrderedBeatmapDetails> loadedLevelsUnsorted = new List <OrderedBeatmapDetails>(levels.Length); List <SongDataCoreDataStatus> sdcErrorStatusList = new List <SongDataCoreDataStatus>(levels.Length); var sw = Stopwatch.StartNew(); List <IEnumerator <OrderedBeatmapDetails> > taskList = new List <IEnumerator <OrderedBeatmapDetails> >(WorkChunkSize); int index = 0; while (index < levels.Length) { if (_isOperationCancelled) { return; } for (int i = 0; i < WorkChunkSize && index < levels.Length; ++index) { _levelsLoadedCount = loadedLevelsUnsorted.Count; IPreviewBeatmapLevel level = levels[index]; string levelID = GetSimplifiedLevelID(level); if (level is IBeatmapLevel beatmapLevel) { loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, new BeatmapDetails(beatmapLevel))); } else if (BeatmapDetailsLoader._cache.ContainsKey(levelID)) { loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, BeatmapDetailsLoader._cache[levelID])); } else if (level is CustomPreviewBeatmapLevel customLevel) { SongDataCoreDataStatus status = SongDataCoreTweaks.GetBeatmapDetails(customLevel, out var beatmapDetails); if (status == SongDataCoreDataStatus.Success) { // load the beatmap details manually if some data from BeatSaver is incomplete if (beatmapDetails.DifficultyBeatmapSets.Any(set => set.DifficultyBeatmaps.Any(diff => diff.NoteJumpMovementSpeed == 0))) { Logger.log.Debug($"BeatmapDetails object generated for '{beatmapDetails.SongName}' from BeatSaver data has some incomplete fields. " + "Discarding and generating BeatmapDetails object from locally stored information instead"); taskList.Add(GetOrderedCustomBeatmapDetailsCoroutine(customLevel, index)); ++i; } else { loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, beatmapDetails)); BeatmapDetailsLoader._cache[levelID] = beatmapDetails; } } else { if (SongDataCoreTweaks.IsModAvailable) { sdcErrorStatusList.Add(status); } taskList.Add(GetOrderedCustomBeatmapDetailsCoroutine(customLevel, index)); ++i; } } else { // not convertable (possible unbought DLC) loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, null)); } } while (taskList.Any()) { if (_isOperationCancelled) { return; } for (int i = 0; i < taskList.Count; ++i) { IEnumerator <OrderedBeatmapDetails> loadCoroutine = taskList[i]; if (loadCoroutine.MoveNext()) { OrderedBeatmapDetails obd = loadCoroutine.Current; if (obd != null) { loadedLevelsUnsorted.Add(obd); _levelsLoadedCount = loadedLevelsUnsorted.Count; taskList.Remove(loadCoroutine); --i; } } else { taskList.Remove(loadCoroutine); --i; } } } } sw.Stop(); Logger.log.Debug($"Finished loading the details of {loadedLevelsUnsorted.Count} beatmaps (took {sw.ElapsedMilliseconds / 1000f} seconds)"); int notLoadedCount = loadedLevelsUnsorted.Count(x => x.Details == null); if (notLoadedCount > 0) { Logger.log.Warn($"Unable to load the beatmap details for {notLoadedCount} songs"); } if (sdcErrorStatusList.Count > 0) { // NOTE: this will need to be updated if i ever add more error status markers Logger.log.Debug($"Unable to retrieve some data from SongDataCore: (" + $"NoData = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.NoData)}, " + $"InvalidBPM = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidBPM)}, " + $"InvalidDuration = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidDuration)}, " + $"InvalidCharacteristicString = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidCharacteristicString)}, " + $"InvalidDifficultyString = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidDifficultyString)}, " + $"ExceptionThrown = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.ExceptionThrown)})"); } HMMainThreadDispatcher.instance.Enqueue(delegate() { _thread = null; onFinish.Invoke(loadedLevelsUnsorted.Select(x => x.Details).ToArray()); }); } catch (Exception e) { Logger.log.Warn("Unexpected exception occurred in loading thread"); Logger.log.Debug(e); } }
private IEnumerator LoadBeatmapsCoroutine(IPreviewBeatmapLevel[] levels, Action <int> updateCallback, Action <BeatmapDetails[]> onFinish) { List <IEnumerator <OrderedBeatmapDetails> > taskList = new List <IEnumerator <OrderedBeatmapDetails> >(WorkChunkSize); List <OrderedBeatmapDetails> loadedLevelsUnsorted = new List <OrderedBeatmapDetails>(levels.Length); // reocrd errors from SongDataCore for logging List <SongDataCoreDataStatus> sdcErrorStatusList = new List <SongDataCoreDataStatus>(levels.Length); var sw = Stopwatch.StartNew(); int index = 0; long elapsed = 0; while (index < levels.Length) { if (updateCallback != null && elapsed - sw.ElapsedMilliseconds > 1000) { updateCallback.Invoke(loadedLevelsUnsorted.Count); elapsed = sw.ElapsedMilliseconds; } int startingIndex = index; for (int i = 0; i < WorkChunkSize && index < levels.Length && index - startingIndex < WorkQueryChunkSize; ++index) { IPreviewBeatmapLevel level = levels[index]; string levelID = GetSimplifiedLevelID(level); if (level is IBeatmapLevel beatmapLevel) { loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, new BeatmapDetails(beatmapLevel))); } else if (BeatmapDetailsLoader._cache.ContainsKey(levelID)) { loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, BeatmapDetailsLoader._cache[levelID])); } else if (level is CustomPreviewBeatmapLevel customLevel) { SongDataCoreDataStatus status = SongDataCoreTweaks.GetBeatmapDetails(customLevel, out var beatmapDetails); if (status == SongDataCoreDataStatus.Success) { // load the beatmap details manually if some data from BeatSaver is incomplete if (beatmapDetails.DifficultyBeatmapSets.Any(set => set.DifficultyBeatmaps.Any(diff => diff.NoteJumpMovementSpeed == 0))) { Logger.log.Debug($"BeatmapDetails object generated for '{beatmapDetails.SongName}' from BeatSaver data has some incomplete fields. " + "Discarding and generating BeatmapDetails object from locally stored information instead"); taskList.Add(GetOrderedCustomBeatmapDetailsCoroutine(customLevel, index)); ++i; } else { loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, beatmapDetails)); BeatmapDetailsLoader._cache[levelID] = beatmapDetails; } } else { if (SongDataCoreTweaks.IsModAvailable) { sdcErrorStatusList.Add(status); } taskList.Add(GetOrderedCustomBeatmapDetailsCoroutine(customLevel, index)); ++i; } } else { // add null details object if not convertable (possibly unbought DLC?) loadedLevelsUnsorted.Add(new OrderedBeatmapDetails(index, null)); } } if (taskList.Any()) { yield return(null); } while (taskList.Any()) { for (int i = 0; i < taskList.Count; ++i) { IEnumerator <OrderedBeatmapDetails> loadCoroutine = taskList[i]; if (loadCoroutine.MoveNext()) { OrderedBeatmapDetails obd = loadCoroutine.Current; if (obd != null) { loadedLevelsUnsorted.Add(obd); taskList.Remove(loadCoroutine); --i; } } else { taskList.Remove(loadCoroutine); --i; } } if (taskList.Any()) { yield return(null); } } if (index < levels.Length) { yield return(null); } } sw.Stop(); Logger.log.Debug($"Finished loading the details of {loadedLevelsUnsorted.Count} beatmaps (took {sw.ElapsedMilliseconds / 1000f} seconds)"); int notLoadedCount = loadedLevelsUnsorted.Count(x => x.Details == null); if (notLoadedCount > 0) { Logger.log.Warn($"Unable to load the beatmap details for {notLoadedCount} songs"); } if (sdcErrorStatusList.Count > 0) { // NOTE: this will need to be updated if i ever add more error status markers Logger.log.Debug($"Unable to retrieve some data from SongDataCore: (" + $"NoData = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.NoData)}, " + $"InvalidBPM = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidBPM)}, " + $"InvalidDuration = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidDuration)}, " + $"InvalidCharacteristicString = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidCharacteristicString)}, " + $"InvalidDifficultyString = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.InvalidDifficultyString)}, " + $"ExceptionThrown = {sdcErrorStatusList.Count(x => x == SongDataCoreDataStatus.ExceptionThrown)})"); } // all beatmaps are loaded, sort to maintain order loadedLevelsUnsorted.Sort((x, y) => x.Position - y.Position); _loadingCoroutine = null; onFinish.Invoke(loadedLevelsUnsorted.Select(x => x.Details).ToArray()); }