From c57c82bdd16ae0747368767fa4b0e25a0903c6f1 Mon Sep 17 00:00:00 2001 From: PopSlime Date: Sun, 9 Apr 2023 22:11:29 +0800 Subject: [PATCH] Add progress tracking for chart loading. --- Assets/Cryville/Common/Coroutine.cs | 26 +++ Assets/Cryville/Common/Coroutine.cs.meta | 11 ++ Assets/Cryville/Crtr/ChartPlayer.cs | 242 ++++++++++++----------- Assets/Cryville/Crtr/StateBase.cs | 13 +- 4 files changed, 178 insertions(+), 114 deletions(-) create mode 100644 Assets/Cryville/Common/Coroutine.cs create mode 100644 Assets/Cryville/Common/Coroutine.cs.meta diff --git a/Assets/Cryville/Common/Coroutine.cs b/Assets/Cryville/Common/Coroutine.cs new file mode 100644 index 0000000..6a58281 --- /dev/null +++ b/Assets/Cryville/Common/Coroutine.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Diagnostics; + +namespace Cryville.Common { + public class Coroutine { + readonly IEnumerator _enumerator; + readonly Stopwatch _stopwatch = new Stopwatch(); + public float Progress { get; private set; } + public Coroutine(IEnumerator enumerator) { + _enumerator = enumerator; + } + public bool TickOnce() { + if (!_enumerator.MoveNext()) return false; + Progress = _enumerator.Current; + return true; + } + public bool Tick(double minTime) { + _stopwatch.Restart(); + while (_stopwatch.Elapsed.TotalSeconds < minTime) { + if (!_enumerator.MoveNext()) return false; + Progress = _enumerator.Current; + } + return true; + } + } +} diff --git a/Assets/Cryville/Common/Coroutine.cs.meta b/Assets/Cryville/Common/Coroutine.cs.meta new file mode 100644 index 0000000..c0be3d0 --- /dev/null +++ b/Assets/Cryville/Common/Coroutine.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 387adc7d494be0147b7cb930bc2e726b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Cryville/Crtr/ChartPlayer.cs b/Assets/Cryville/Crtr/ChartPlayer.cs index 4533845..5b7a178 100644 --- a/Assets/Cryville/Crtr/ChartPlayer.cs +++ b/Assets/Cryville/Crtr/ChartPlayer.cs @@ -16,7 +16,8 @@ using UnityEngine; using UnityEngine.Networking; using UnityEngine.SceneManagement; using UnityEngine.Scripting; -using diag = System.Diagnostics; +using Coroutine = Cryville.Common.Coroutine; +using Stopwatch = System.Diagnostics.Stopwatch; using Logger = Cryville.Common.Logger; namespace Cryville.Crtr { @@ -30,14 +31,6 @@ namespace Cryville.Crtr { Dictionary texs; public static Dictionary frames; - readonly Queue texLoadQueue = new Queue(); -#if UNITY_5_4_OR_NEWER - DownloadHandlerTexture texHandler; - UnityWebRequest texLoader = null; -#else - WWW texLoader = null; -#endif - EventBus cbus; EventBus bbus; EventBus tbus; @@ -88,7 +81,6 @@ namespace Cryville.Crtr { status = GameObject.Find("Status").GetComponent(); - texHandler = new DownloadHandlerTexture(); #if BUILD try { Play(); @@ -109,18 +101,32 @@ namespace Cryville.Crtr { if (tbus != null) tbus.Dispose(); if (nbus != null) nbus.Dispose(); if (loadThread != null) loadThread.Abort(); - if (texLoader != null) texLoader.Dispose(); if (inputProxy != null) inputProxy.Dispose(); if (texs != null) foreach (var t in texs) Texture.Destroy(t.Value); GC.Collect(); } + Coroutine texLoader; bool texloaddone; - diag::Stopwatch texloadtimer = new diag::Stopwatch(); + Coroutine prehandler; int forceSyncFrames; double atime0; void Update() { if (started) GameUpdate(); + else if (prehandler != null) { + try { + if (!prehandler.Tick(1.0 / Application.targetFrameRate)) { + prehandler = null; + started = true; + } + } + catch (Exception ex) { + Game.LogException("Load/Prehandle", "An error occured while prehandling the data", ex); + Popup.CreateException(ex); + prehandler = null; + Stop(); + } + } else if (loadThread != null) LoadUpdate(); if (logEnabled) LogUpdate(); else Game.MainLogger.Enumerate((level, module, msg) => { }); @@ -171,55 +177,7 @@ namespace Cryville.Crtr { } } void LoadUpdate() { - if (texLoader != null) { - string url = texLoader.url; - string name = StringUtils.TrimExt(url.Substring(url.LastIndexOfAny(new char[] {'/', '\\'}) + 1)); -#if UNITY_5_4_OR_NEWER - if (texLoader.isDone) { - if (texHandler.isDone) { - var tex = texHandler.texture; - tex.wrapMode = TextureWrapMode.Clamp; - if (frames.ContainsKey(name)) { - Logger.Log("main", 3, "Load/Prehandle", "Duplicated texture name: {0}", name); - } - else { - frames.Add(name, new SpriteFrame(tex)); - } - texs.Add(name, tex); - } - else { - Logger.Log("main", 4, "Load/Prehandle", "Unable to load texture: {0}", name); - } - texLoader.Dispose(); - texHandler.Dispose(); - texLoader = null; - } -#else - if (texLoader.isDone) { - var tex = texLoader.texture; - tex.wrapMode = TextureWrapMode.Clamp; - texs.Add(name, tex); - texLoader.Dispose(); - texLoader = null; - } -#endif - } - if (texLoader == null) { - if (texLoadQueue.Count > 0) { -#if UNITY_5_4_OR_NEWER - texHandler = new DownloadHandlerTexture(); - texLoader = new UnityWebRequest(Game.FileProtocolPrefix + texLoadQueue.Dequeue(), "GET", texHandler, null); - texLoader.SendWebRequest(); -#else - texLoader = new WWW(Game.FileProtocolPrefix + texLoadQueue.Dequeue()); -#endif - } - else if (!texloaddone) { - texloaddone = true; - texloadtimer.Stop(); - Logger.Log("main", 1, "Load/MainThread", "Main thread done ({0}ms)", texloadtimer.Elapsed.TotalMilliseconds); - } - } + if (!texloaddone) texLoader.Tick(1.0 / Application.targetFrameRate); if (!loadThread.IsAlive) { if (threadException != null) { Logger.Log("main", 4, "Load/MainThread", "Load failed"); @@ -229,8 +187,8 @@ namespace Cryville.Crtr { ReturnToMenu(); #endif } - else if (texLoader == null) { - Prehandle(); + else if (texloaddone) { + prehandler = new Coroutine(Prehandle()); loadThread = null; } } @@ -292,6 +250,13 @@ namespace Cryville.Crtr { UnityEngine.Profiling.Profiler.GetTotalReservedMemory() #endif ); + if (loadThread != null) { + statusbuf.AppendFormat( + "\n(Loading textures) Progress: {0:P}\n(Loading files) Progress: {1:P}", + texLoader.Progress, loadPregress + ); + } + if (prehandler != null) statusbuf.AppendFormat("\n(Prehandling) Progress: {0:P}", prehandler.Progress); if (started) { statusbuf.AppendFormat( "\nStates: c{0} / b{1}\nPools: RMV {2}, MC {3}", @@ -357,7 +322,7 @@ namespace Cryville.Crtr { public void TogglePlay() { if (started) Stop(); else { - if (loadThread == null) Play(); + if (texLoader == null && loadThread == null && prehandler == null) Play(); else Logger.Log("main", 2, "Load/MainThread", "The chart is currently loading"); } } @@ -446,54 +411,103 @@ namespace Cryville.Crtr { }); Logger.Log("main", 0, "Load/MainThread", "Loading textures..."); - texloadtimer = new diag::Stopwatch(); - texloadtimer.Start(); frames = new Dictionary(); texs = new Dictionary(); var skinDir = skinFile.Directory.FullName; + var texLoadQueue = new List(); foreach (var f in skin.frames) { - texLoadQueue.Enqueue(Path.Combine(skinDir, f)); + texLoadQueue.Add(Path.Combine(skinDir, f)); } + texLoader = new Coroutine(LoadTextures(texLoadQueue)); } - void Prehandle() { - try { - diag::Stopwatch timer = new diag::Stopwatch(); - timer.Reset(); timer.Start(); - Logger.Log("main", 0, "Load/Prehandle", "Prehandling (iteration 2)"); - cbus.BroadcastPreInit(); - Logger.Log("main", 0, "Load/Prehandle", "Prehandling (iteration 3)"); - using (var pbus = cbus.Clone(17)) { - pbus.Forward(); + IEnumerator LoadTextures(List queue) { + Stopwatch stopwatch = new Stopwatch(); + stopwatch.Start(); +#if UNITY_5_4_OR_NEWER + DownloadHandlerTexture texHandler = null; + UnityWebRequest texLoader = null; +#else + WWW texLoader = null; +#endif + for (int i = 0; i < queue.Count; i++) { +#if UNITY_5_4_OR_NEWER + texHandler = new DownloadHandlerTexture(); + texLoader = new UnityWebRequest(Game.FileProtocolPrefix + queue[i], "GET", texHandler, null); + texLoader.SendWebRequest(); +#else + texLoader = new WWW(Game.FileProtocolPrefix + queue[i]); +#endif + while (!texLoader.isDone) yield return (float)i / queue.Count; +#if UNITY_5_4_OR_NEWER + if (texHandler.isDone) { + string url = texLoader.url; + string name = StringUtils.TrimExt(url.Substring(url.LastIndexOfAny(new char[] {'/', '\\'}) + 1)); + var tex = texHandler.texture; + tex.wrapMode = TextureWrapMode.Clamp; + if (frames.ContainsKey(name)) { + Logger.Log("main", 3, "Load/Prehandle", "Duplicated texture name: {0}", name); + } + else { + frames.Add(name, new SpriteFrame(tex)); + } + texs.Add(name, tex); } - Logger.Log("main", 0, "Load/Prehandle", "Prehandling (iteration 4)"); - cbus.BroadcastPostInit(); - inputProxy.Activate(); - if (logEnabled && Settings.Default.HideLogOnPlay) ToggleLogs(); - Logger.Log("main", 0, "Load/Prehandle", "Cleaning up"); - GC.Collect(); - if (disableGC) GarbageCollector.GCMode = GarbageCollector.Mode.Disabled; - cbus.ForwardByTime(startOffset); - bbus.ForwardByTime(startOffset); - timer.Stop(); - Logger.Log("main", 1, "Load/Prehandle", "Prehandling done ({0}ms)", timer.Elapsed.TotalMilliseconds); - if (Settings.Default.ClearLogOnPlay) { - logEntries.Clear(); - logsLength = 0; - Game.MainLogger.Enumerate((level, module, msg) => { }); - logs.text = ""; + else { + Logger.Log("main", 4, "Load/Prehandle", "Unable to load texture: {0}", name); } - Game.AudioSequencer.SeekTime(startOffset, SeekOrigin.Current); - Game.AudioSequencer.Playing = true; - atime0 = Game.AudioClient.BufferPosition - startOffset; - inputProxy.SyncTime(cbus.Time); - started = true; + texLoader.Dispose(); + texHandler.Dispose(); +#else + string url = texLoader.url; + string name = StringUtils.TrimExt(url.Substring(url.LastIndexOfAny(new char[] {'/', '\\'}) + 1)); + var tex = texLoader.texture; + tex.wrapMode = TextureWrapMode.Clamp; + texs.Add(name, tex); + texLoader.Dispose(); + texLoader = null; +#endif } - catch (Exception ex) { - Game.LogException("Load/Prehandle", "An error occured while prehandling the data", ex); - Popup.CreateException(ex); - Stop(); + texloaddone = true; + stopwatch.Stop(); + Logger.Log("main", 1, "Load/MainThread", "Main thread done ({0}ms)", stopwatch.Elapsed.TotalMilliseconds); + yield return 1; + } + + IEnumerator Prehandle() { + Stopwatch timer = new Stopwatch(); + timer.Reset(); timer.Start(); + Logger.Log("main", 0, "Load/Prehandle", "Prehandling (iteration 2)"); yield return .00f; + cbus.BroadcastPreInit(); + Logger.Log("main", 0, "Load/Prehandle", "Prehandling (iteration 3)"); yield return .05f; + using (var pbus = cbus.Clone(17)) { + while (pbus.Time != double.PositiveInfinity) { + pbus.ForwardOnce(); + yield return (float)pbus.EventId / pbus.EventCount * .80f + .05f; + } } + Logger.Log("main", 0, "Load/Prehandle", "Prehandling (iteration 4)"); yield return .85f; + cbus.BroadcastPostInit(); + Logger.Log("main", 0, "Load/Prehandle", "Seeking to start offset"); yield return .90f; + cbus.ForwardByTime(startOffset); + bbus.ForwardByTime(startOffset); + Game.AudioSequencer.SeekTime(startOffset, SeekOrigin.Current); + Logger.Log("main", 0, "Load/Prehandle", "Cleaning up"); yield return .95f; + if (logEnabled && Settings.Default.HideLogOnPlay) ToggleLogs(); + inputProxy.Activate(); + GC.Collect(); + if (disableGC) GarbageCollector.GCMode = GarbageCollector.Mode.Disabled; + timer.Stop(); + Logger.Log("main", 1, "Load/Prehandle", "Prehandling done ({0}ms)", timer.Elapsed.TotalMilliseconds); yield return 1; + if (Settings.Default.ClearLogOnPlay) { + logEntries.Clear(); + logsLength = 0; + Game.MainLogger.Enumerate((level, module, msg) => { }); + logs.text = ""; + } + Game.AudioSequencer.Playing = true; + atime0 = Game.AudioClient.BufferPosition - startOffset; + inputProxy.SyncTime(cbus.Time); } public void Stop() { @@ -545,12 +559,13 @@ namespace Cryville.Crtr { Exception threadException; #if !NO_THREAD Thread loadThread = null; - diag::Stopwatch workerTimer; + volatile float loadPregress; + Stopwatch workerTimer; #endif void Load(object _info) { var info = (LoadInfo)_info; try { - workerTimer = new diag::Stopwatch(); + workerTimer = new Stopwatch(); workerTimer.Start(); LoadChart(info); workerTimer.Stop(); @@ -573,20 +588,20 @@ namespace Cryville.Crtr { if (chart.format != 2) throw new FormatException("Invalid chart file format version"); - etor = new PdtEvaluator(); + etor = new PdtEvaluator(); loadPregress = .05f; LoadRuleset(info.rulesetFile); - Logger.Log("main", 0, "Load/WorkerThread", "Applying ruleset (iteration 1)"); + Logger.Log("main", 0, "Load/WorkerThread", "Applying ruleset (iteration 1)"); loadPregress = .10f; pruleset.PrePatch(chart); - Logger.Log("main", 0, "Load/WorkerThread", "Batching events"); + Logger.Log("main", 0, "Load/WorkerThread", "Batching events"); loadPregress = .20f; var batcher = new EventBatcher(chart); batcher.Forward(); - cbus = batcher.Batch(); + cbus = batcher.Batch(); loadPregress = .30f; LoadSkin(info.skinFile); - Logger.Log("main", 0, "Load/WorkerThread", "Initializing judge and input"); + Logger.Log("main", 0, "Load/WorkerThread", "Initializing judge and input"); loadPregress = .35f; judge = new Judge(this, pruleset); etor.ContextJudge = judge; @@ -596,7 +611,7 @@ namespace Cryville.Crtr { throw new ArgumentException("Input config not completed\nPlease complete the input settings"); } - Logger.Log("main", 0, "Load/WorkerThread", "Attaching handlers"); + Logger.Log("main", 0, "Load/WorkerThread", "Attaching handlers"); loadPregress = .40f; var ch = new ChartHandler(chart, dir); cbus.RootState.AttachHandler(ch); foreach (var gs in cbus.RootState.Children) { @@ -614,17 +629,18 @@ namespace Cryville.Crtr { } } cbus.AttachSystems(pskin, judge); - Logger.Log("main", 0, "Load/WorkerThread", "Prehandling (iteration 1)"); + Logger.Log("main", 0, "Load/WorkerThread", "Prehandling (iteration 1)"); loadPregress = .60f; using (var pbus = cbus.Clone(16)) { pbus.Forward(); } - Logger.Log("main", 0, "Load/WorkerThread", "Cloning states (type 1)"); + Logger.Log("main", 0, "Load/WorkerThread", "Cloning states (type 1)"); loadPregress = .70f; bbus = cbus.Clone(1, -clippingDist); - Logger.Log("main", 0, "Load/WorkerThread", "Cloning states (type 2)"); + Logger.Log("main", 0, "Load/WorkerThread", "Cloning states (type 2)"); loadPregress = .80f; tbus = bbus.Clone(2); - Logger.Log("main", 0, "Load/WorkerThread", "Cloning states (type 3)"); + Logger.Log("main", 0, "Load/WorkerThread", "Cloning states (type 3)"); loadPregress = .90f; nbus = bbus.Clone(3); + loadPregress = 1; } } diff --git a/Assets/Cryville/Crtr/StateBase.cs b/Assets/Cryville/Crtr/StateBase.cs index 4348ad0..a466fb7 100644 --- a/Assets/Cryville/Crtr/StateBase.cs +++ b/Assets/Cryville/Crtr/StateBase.cs @@ -13,7 +13,11 @@ namespace Cryville.Crtr { /// /// The index of the event to be handled next. /// - protected int EventId; + public int EventId { get; protected set; } + /// + /// The event count. + /// + public int EventCount { get { return Events.Count; } } /// /// The event list. /// @@ -66,6 +70,13 @@ namespace Cryville.Crtr { ForwardToTime(double.PositiveInfinity); } + /// + /// Forwards to the next event. + /// + public void ForwardOnce() { + ForwardOnceToTime(double.PositiveInfinity); + } + /// /// Forwards the time by the specified span and walks through all the encountered events. ///