From bfe22edc19763ae5c8908b984a305e58fe69ff78 Mon Sep 17 00:00:00 2001 From: Nicolas Gnyra Date: Tue, 3 Dec 2024 07:52:18 -0500 Subject: [PATCH 1/3] Fix various exceptions when using a debug Unity build --- BetterSongSearch.csproj | 2 +- UI/DownloadHistoryView.cs | 76 ++++++++++++++-------------- UI/SelectedSongView.cs | 14 +++-- UI/Views/SplitViews/UploadDetails.cs | 13 +++-- Util/SongDownloader.cs | 10 ++-- Util/UnityWebrequestWrapper.cs | 50 +++++++----------- 6 files changed, 79 insertions(+), 86 deletions(-) diff --git a/BetterSongSearch.csproj b/BetterSongSearch.csproj index 3a592a8..72a3017 100644 --- a/BetterSongSearch.csproj +++ b/BetterSongSearch.csproj @@ -125,7 +125,7 @@ $(BeatSaberDir)\Libs\Newtonsoft.Json.dll - E:\SteamLibrary\steamapps\common\Beat Saber\Libs\protobuf-net.dll + $(BeatSaberDir)\Libs\protobuf-net.dll $(BeatSaberDir)\Beat Saber_Data\Managed\SegmentedControl.dll diff --git a/UI/DownloadHistoryView.cs b/UI/DownloadHistoryView.cs index 7d35c56..39cdc67 100644 --- a/UI/DownloadHistoryView.cs +++ b/UI/DownloadHistoryView.cs @@ -84,47 +84,45 @@ public async void ProcessDownloads(bool forceTableReload = false) { RefreshTable(true); - await Task.Run(async () => { - void errored(string message) { - firstEntry.status = DownloadHistoryEntry.DownloadStatus.Failed; - firstEntry.statusDetails = $": {message}"; - firstEntry.retries = 69; - } + void errored(string message) { + firstEntry.status = DownloadHistoryEntry.DownloadStatus.Failed; + firstEntry.statusDetails = $": {message}"; + firstEntry.retries = 69; + } - try { - var updateRateLimiter = new Stopwatch(); - updateRateLimiter.Start(); - - await SongDownloader.BeatmapDownload(firstEntry, BSSFlowCoordinator.closeCancelSource.Token, (float progress) => { - if(updateRateLimiter.ElapsedMilliseconds < 50) - return; - - firstEntry.statusDetails = string.Format("({0:0%}{1})", progress, firstEntry.retries == 0 ? "" : $", retry #{firstEntry.retries} / {RETRY_COUNT}"); - firstEntry.downloadProgress = progress; - - updateRateLimiter.Restart(); - - if(firstEntry.UpdateProgressHandler != null) - IPA.Utilities.Async.UnityMainThreadTaskScheduler.Factory.StartNew(firstEntry.UpdateProgressHandler); - }); - - firstEntry.status = DownloadHistoryEntry.DownloadStatus.Downloaded; - firstEntry.statusDetails = ""; - } catch(FileNotFoundException) { - errored("File not Found, Uploader probably deleted it"); - } catch(TaskCanceledException) { - errored("Download was cancelled"); - } catch(Exception ex) { - if(!(ex is TaskCanceledException)) { - Plugin.Log.Warn("Download failed:"); - Plugin.Log.Warn(ex); - } - - firstEntry.status = DownloadHistoryEntry.DownloadStatus.Failed; - firstEntry.statusDetails = $"{(firstEntry.retries < 3 ? "(Will retry)" : "")}: Details in log, {ex.Message} ({ex.GetType().Name})"; + try { + var updateRateLimiter = new Stopwatch(); + updateRateLimiter.Start(); + + await SongDownloader.BeatmapDownload(firstEntry, BSSFlowCoordinator.closeCancelSource.Token, (float progress) => { + if(updateRateLimiter.ElapsedMilliseconds < 50) + return; + + firstEntry.statusDetails = string.Format("({0:0%}{1})", progress, firstEntry.retries == 0 ? "" : $", retry #{firstEntry.retries} / {RETRY_COUNT}"); + firstEntry.downloadProgress = progress; + + updateRateLimiter.Restart(); + + if(firstEntry.UpdateProgressHandler != null) + firstEntry.UpdateProgressHandler(); + }); + + firstEntry.status = DownloadHistoryEntry.DownloadStatus.Downloaded; + firstEntry.statusDetails = ""; + } catch(FileNotFoundException) { + errored("File not Found, Uploader probably deleted it"); + } catch(TaskCanceledException) { + errored("Download was cancelled"); + } catch(Exception ex) { + if(!(ex is TaskCanceledException)) { + Plugin.Log.Warn("Download failed:"); + Plugin.Log.Warn(ex); } - firstEntry.downloadProgress = 1f; - }); + + firstEntry.status = DownloadHistoryEntry.DownloadStatus.Failed; + firstEntry.statusDetails = $"{(firstEntry.retries < 3 ? "(Will retry)" : "")}: Details in log, {ex.Message} ({ex.GetType().Name})"; + } + firstEntry.downloadProgress = 1f; if(firstEntry.status == DownloadHistoryEntry.DownloadStatus.Downloaded) { // NESTING HELLLL diff --git a/UI/SelectedSongView.cs b/UI/SelectedSongView.cs index 8ecbb75..b688b4c 100644 --- a/UI/SelectedSongView.cs +++ b/UI/SelectedSongView.cs @@ -46,6 +46,12 @@ void OnEnable() { if(selectedSong != null) SetIsDownloaded(selectedSong.CheckIsDownloaded(), selectedSong.CheckIsDownloadable()); + + if(soloFreePlayFlowCoordinator == null) + soloFreePlayFlowCoordinator = FindObjectOfType(); + + if(multiplayerLevelSelectionFlowCoordinator == null) + multiplayerLevelSelectionFlowCoordinator = FindObjectOfType(); } static internal SongPreviewPlayer songPreviewPlayer { get; private set; } = null; @@ -116,7 +122,9 @@ internal async void SetSelectedSong(SongSearchSong song, bool selectInTableIfPos await Task.WhenAll(new[] { BSSFlowCoordinator.assetLoader.LoadCoverAsync(song.detailsSong, songAssetLoadCanceller.Token).ContinueWith( x => { coverImage.sprite = x.Result; }, - TaskContinuationOptions.OnlyOnRanToCompletion + CancellationToken.None, + TaskContinuationOptions.OnlyOnRanToCompletion, + TaskScheduler.FromCurrentSynchronizationContext() ), !PluginConfig.Instance.loadSongPreviews ? Task.FromResult(1) : BSSFlowCoordinator.assetLoader.LoadPreviewAsync(song.detailsSong, songAssetLoadCanceller.Token).ContinueWith( @@ -171,8 +179,8 @@ internal void PlayQueuedSongToPlay() { songToPlayAfterLoading = null; } - readonly SoloFreePlayFlowCoordinator soloFreePlayFlowCoordinator = FindObjectOfType(); - readonly MultiplayerLevelSelectionFlowCoordinator multiplayerLevelSelectionFlowCoordinator = FindObjectOfType(); + SoloFreePlayFlowCoordinator soloFreePlayFlowCoordinator; + MultiplayerLevelSelectionFlowCoordinator multiplayerLevelSelectionFlowCoordinator; static readonly ConstructorInfo LevelSelectionFlowCoordinator_State = AccessTools.FirstConstructor(typeof(LevelSelectionFlowCoordinator.State), x => x.GetParameters().Length == 4); [UIAction("Play")] void _Play() => PlaySong(); diff --git a/UI/Views/SplitViews/UploadDetails.cs b/UI/Views/SplitViews/UploadDetails.cs index 9c96804..74409ca 100644 --- a/UI/Views/SplitViews/UploadDetails.cs +++ b/UI/Views/SplitViews/UploadDetails.cs @@ -3,7 +3,6 @@ using HMUI; using System; using System.Linq; -using System.Threading.Tasks; using TMPro; namespace BetterSongSearch.UI.SplitViews { @@ -27,12 +26,12 @@ public async void Populate(SongSearchSong selectedSong) { songDetailsLoading.gameObject.SetActive(true); - var desc = await Task.Run(async () => { - try { - return await BSSFlowCoordinator.assetLoader.GetSongDescription(selectedSong.detailsSong.key, BSSFlowCoordinator.closeCancelSource.Token); - } catch { } - return "Failed to load description"; - }); + string desc; + try { + desc = await BSSFlowCoordinator.assetLoader.GetSongDescription(selectedSong.detailsSong.key, BSSFlowCoordinator.closeCancelSource.Token); + } catch { + desc = "Failed to load description"; + } songDetailsLoading.gameObject.SetActive(false); selectedSongDescription.text = desc; diff --git a/Util/SongDownloader.cs b/Util/SongDownloader.cs index 9af150f..a90bd6f 100644 --- a/Util/SongDownloader.cs +++ b/Util/SongDownloader.cs @@ -1,4 +1,5 @@ -using System; +using IPA.Utilities.Async; +using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; @@ -33,8 +34,7 @@ public static async Task BeatmapDownload(DownloadHistoryEntry entry, Cancellatio entry.status = DownloadHistoryEntry.DownloadStatus.Extracting; progressCb(0); - // Not async'ing this as BeatmapDownload() is supposed to be called in a task - ExtractZip(s, folderName, t.Token, progressCb); + await Task.Run(() => ExtractZip(s, folderName, t.Token, progressCb)); } } @@ -79,7 +79,7 @@ static unsafe void ExtractZip(Stream zipStream, string basePath, CancellationTok progress++; } - progressCb((float)++progress / steps); + UnityMainThreadTaskScheduler.Factory.StartNew(() => progressCb((float)++progress / steps)); } } @@ -114,7 +114,7 @@ static unsafe void ExtractZip(Stream zipStream, string basePath, CancellationTok } } - progressCb((float)++progress / steps); + UnityMainThreadTaskScheduler.Factory.StartNew(() => progressCb((float)++progress / steps)); } } finally { foreach(var item in files) diff --git a/Util/UnityWebrequestWrapper.cs b/Util/UnityWebrequestWrapper.cs index d2f9a13..19c1206 100644 --- a/Util/UnityWebrequestWrapper.cs +++ b/Util/UnityWebrequestWrapper.cs @@ -21,42 +21,30 @@ public static async Task Download(string url, DownloadHandler handler, Can var req = www.SendWebRequest(); - var startThread = SynchronizationContext.Current; - - Action post; - - if(startThread == null) { - post = a => a(); - } else { - post = a => startThread.Send(_ => a(), null); - } - - await Task.Run(() => { - var lastState = 0f; - var timeouter = new System.Diagnostics.Stopwatch(); - timeouter.Start(); - - while(!req.isDone) { - if(token.IsCancellationRequested) { - post(www.Abort); - throw new TaskCanceledException(); - } + var lastState = 0f; + var timeouter = new System.Diagnostics.Stopwatch(); + timeouter.Start(); + + while(!req.isDone) { + if(token.IsCancellationRequested) { + www.Abort(); + throw new TaskCanceledException(); + } - if(timeouter.ElapsedMilliseconds > 50000 || (lastState == 0 && timeouter.ElapsedMilliseconds > 6000)) { - post(www.Abort); - throw new TimeoutException(); - } + if(timeouter.ElapsedMilliseconds > 50000 || (lastState == 0 && timeouter.ElapsedMilliseconds > 6000)) { + www.Abort(); + throw new TimeoutException(); + } - Thread.Sleep(20); + await Task.Delay(20); - lastState = www.downloadProgress; + lastState = www.downloadProgress; - if(progressCb != null && lastState > 0) - post(() => progressCb(lastState)); - } - }); + if(progressCb != null && lastState > 0) + progressCb(lastState); + } - return www.isDone && !www.isHttpError && !www.isNetworkError; + return www.isDone && www.result == UnityWebRequest.Result.Success; } finally { if(www != null && uwr == null) www.Dispose(); From 0304f25caefc1918f101fefdfa11a653197884f1 Mon Sep 17 00:00:00 2001 From: Kinsi Date: Thu, 5 Dec 2024 01:40:37 +0100 Subject: [PATCH 2/3] Post to the origin thread rather than strictly going to main thread --- Util/SongDownloader.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Util/SongDownloader.cs b/Util/SongDownloader.cs index a90bd6f..da972ec 100644 --- a/Util/SongDownloader.cs +++ b/Util/SongDownloader.cs @@ -34,7 +34,9 @@ public static async Task BeatmapDownload(DownloadHistoryEntry entry, Cancellatio entry.status = DownloadHistoryEntry.DownloadStatus.Extracting; progressCb(0); - await Task.Run(() => ExtractZip(s, folderName, t.Token, progressCb)); + var sThread = SynchronizationContext.Current; + + await Task.Run(() => ExtractZip(s, folderName, t.Token, (p) => sThread.Post(_ => progressCb(p), null))); } } @@ -79,7 +81,7 @@ static unsafe void ExtractZip(Stream zipStream, string basePath, CancellationTok progress++; } - UnityMainThreadTaskScheduler.Factory.StartNew(() => progressCb((float)++progress / steps)); + progressCb((float)++progress / steps); } } @@ -114,7 +116,7 @@ static unsafe void ExtractZip(Stream zipStream, string basePath, CancellationTok } } - UnityMainThreadTaskScheduler.Factory.StartNew(() => progressCb((float)++progress / steps)); + progressCb((float)++progress / steps); } } finally { foreach(var item in files) From f6b3f33977f2492510093cc14c39c5211dd00454 Mon Sep 17 00:00:00 2001 From: Kinsi Date: Thu, 5 Dec 2024 01:45:01 +0100 Subject: [PATCH 3/3] Better timeout handling --- Util/UnityWebrequestWrapper.cs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Util/UnityWebrequestWrapper.cs b/Util/UnityWebrequestWrapper.cs index 19c1206..9c86a31 100644 --- a/Util/UnityWebrequestWrapper.cs +++ b/Util/UnityWebrequestWrapper.cs @@ -31,17 +31,22 @@ public static async Task Download(string url, DownloadHandler handler, Can throw new TaskCanceledException(); } - if(timeouter.ElapsedMilliseconds > 50000 || (lastState == 0 && timeouter.ElapsedMilliseconds > 6000)) { + await Task.Delay(20); + + if(lastState == www.downloadProgress) { + if(timeouter.ElapsedMilliseconds < (lastState == 0 ? 6000 : 10000)) + continue; + www.Abort(); throw new TimeoutException(); } - await Task.Delay(20); - lastState = www.downloadProgress; if(progressCb != null && lastState > 0) progressCb(lastState); + + timeouter.Restart(); } return www.isDone && www.result == UnityWebRequest.Result.Success;