710 lines
23 KiB
C#
710 lines
23 KiB
C#
using Cryville.Common;
|
|
using Cryville.Common.Buffers;
|
|
using Cryville.Common.Logging;
|
|
using Cryville.Crtr.Config;
|
|
using Cryville.Crtr.Event;
|
|
using Cryville.Crtr.Ruleset;
|
|
using Cryville.Crtr.Skin;
|
|
using Cryville.Crtr.UI;
|
|
using Newtonsoft.Json;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Text.Formatting;
|
|
using System.Threading;
|
|
using TMPro;
|
|
using UnityEngine;
|
|
using UnityEngine.Networking;
|
|
using UnityEngine.SceneManagement;
|
|
using UnityEngine.Scripting;
|
|
using Coroutine = Cryville.Common.Coroutine;
|
|
using Stopwatch = System.Diagnostics.Stopwatch;
|
|
|
|
namespace Cryville.Crtr {
|
|
public class ChartPlayer : MonoBehaviour {
|
|
#region Fields
|
|
Chart chart;
|
|
SkinDefinition skin;
|
|
public static PdtSkin pskin;
|
|
RulesetDefinition ruleset;
|
|
PdtRuleset pruleset;
|
|
Dictionary<string, Texture2D> texs;
|
|
public static Dictionary<string, SpriteFrame> frames;
|
|
|
|
EventBus cbus;
|
|
EventBus bbus;
|
|
EventBus tbus;
|
|
EventBus nbus;
|
|
InputProxy inputProxy;
|
|
Judge judge;
|
|
public static EffectManager effectManager;
|
|
bool started = false;
|
|
|
|
static bool initialized;
|
|
TextMeshProUGUI logs;
|
|
TextMeshProUGUI status;
|
|
BufferedLoggerListener loggerListener;
|
|
|
|
static Vector2 screenSize;
|
|
public static Rect hitRect;
|
|
public static Plane[] frustumPlanes;
|
|
|
|
RulesetConfig _rscfg;
|
|
static bool disableGC = true;
|
|
static float clippingDist = 1f;
|
|
static float renderDist = 6f;
|
|
static double renderStep = 0.05;
|
|
public static double actualRenderStep = 0;
|
|
static bool autoRenderStep = false;
|
|
static float graphicalOffset = 0;
|
|
public static float soundOffset = 0;
|
|
static float startOffset = 0;
|
|
public static int areaJudgePrecision = 16;
|
|
public static float sv = 16f;
|
|
|
|
public static Dictionary<Identifier, MotionRegistry> motionRegistry;
|
|
#endregion
|
|
|
|
#region MonoBehaviour
|
|
void Start() {
|
|
d_addLogEntry = AddLogEntry;
|
|
|
|
var logobj = GameObject.Find("Logs");
|
|
if (logobj != null)
|
|
logs = logobj.GetComponent<TextMeshProUGUI>();
|
|
if (!initialized) {
|
|
Game.Init();
|
|
BuiltinResources.LoadDefault();
|
|
initialized = true;
|
|
}
|
|
OnSettingsUpdate();
|
|
|
|
status = GameObject.Find("Status").GetComponent<TextMeshProUGUI>();
|
|
loggerListener = new BufferedLoggerListener();
|
|
Game.MainLogger.AddListener(loggerListener);
|
|
|
|
Game.SuspendBackgroundTasks();
|
|
|
|
try {
|
|
Play();
|
|
}
|
|
catch (Exception ex) {
|
|
Game.LogException("Load/WorkerThread", "An error occurred while loading the data", ex);
|
|
Popup.CreateException(ex);
|
|
ReturnToMenu();
|
|
}
|
|
}
|
|
|
|
void OnDestroy() {
|
|
cbus?.Dispose();
|
|
bbus?.Dispose();
|
|
tbus?.Dispose();
|
|
nbus?.Dispose();
|
|
loadThread?.Abort();
|
|
inputProxy?.Dispose();
|
|
if (texs != null) foreach (var t in texs) Texture.Destroy(t.Value);
|
|
Game.MainLogger.RemoveListener(loggerListener);
|
|
loggerListener.Dispose();
|
|
GC.Collect();
|
|
}
|
|
|
|
Coroutine texLoader;
|
|
bool texloaddone;
|
|
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 occurred while prehandling the data", ex);
|
|
Popup.CreateException(ex);
|
|
prehandler = null;
|
|
Stop();
|
|
}
|
|
}
|
|
else if (loadThread != null) LoadUpdate();
|
|
if (logEnabled) LogUpdate();
|
|
else loggerListener.Enumerate((level, module, msg) => { });
|
|
}
|
|
void GameUpdate() {
|
|
try {
|
|
if (Screen.width != screenSize.x || Screen.height != screenSize.y)
|
|
throw new InvalidOperationException("Window resized while playing");
|
|
double dt, step;
|
|
if (forceSyncFrames != 0) {
|
|
forceSyncFrames--;
|
|
double target = Game.AudioClient.Position - atime0;
|
|
dt = target - cbus.Time - graphicalOffset;
|
|
step = autoRenderStep ? 1f / Application.targetFrameRate : renderStep;
|
|
inputProxy.SyncTime(target);
|
|
}
|
|
else {
|
|
dt = Time.deltaTime;
|
|
step = autoRenderStep ? Time.smoothDeltaTime : renderStep;
|
|
}
|
|
inputProxy.ForceTick();
|
|
if (paused) return;
|
|
cbus.ForwardByTime(dt);
|
|
bbus.ForwardByTime(dt);
|
|
UnityEngine.Profiling.Profiler.BeginSample("ChartPlayer.Forward");
|
|
UnityEngine.Profiling.Profiler.BeginSample("EventBus.Copy");
|
|
bbus.CopyTo(tbus);
|
|
bbus.CopyTo(nbus);
|
|
UnityEngine.Profiling.Profiler.EndSample();
|
|
actualRenderStep = step;
|
|
|
|
nbus.PreAnchor();
|
|
nbus.StripTempEvents();
|
|
nbus.ForwardStepByTime(clippingDist, step);
|
|
nbus.EndPreGraphicalUpdate();
|
|
nbus.Anchor();
|
|
|
|
tbus.StripTempEvents();
|
|
tbus.ForwardStepByTime(clippingDist, step);
|
|
tbus.ForwardStepByTime(renderDist, step);
|
|
tbus.EndGraphicalUpdate();
|
|
UnityEngine.Profiling.Profiler.EndSample();
|
|
|
|
effectManager.Tick(cbus.Time);
|
|
}
|
|
catch (Exception ex) {
|
|
Game.LogException("Game", "An error occurred while playing", ex);
|
|
Popup.CreateException(ex);
|
|
Stop();
|
|
}
|
|
}
|
|
void LoadUpdate() {
|
|
if (!texloaddone) texLoader.Tick(1.0 / Application.targetFrameRate);
|
|
if (!loadThread.IsAlive) {
|
|
if (threadException != null) {
|
|
Game.MainLogger.Log(4, "Load/MainThread", "Load failed");
|
|
loadThread = null;
|
|
Popup.CreateException(threadException);
|
|
ReturnToMenu();
|
|
}
|
|
else if (texloaddone) {
|
|
if (texLoader == null) Stop();
|
|
else {
|
|
prehandler = new Coroutine(Prehandle());
|
|
texLoader = null;
|
|
}
|
|
loadThread = null;
|
|
}
|
|
}
|
|
}
|
|
readonly StringBuffer statusbuf = new();
|
|
readonly StringBuffer logsbuf = new();
|
|
readonly List<string> logEntries = new();
|
|
readonly ArrayPool<char> logBufferPool = new();
|
|
int logsLength = 0;
|
|
LogHandler d_addLogEntry;
|
|
void AddLogEntry(int level, string module, string msg) {
|
|
string color = level switch {
|
|
0 => "#888888",
|
|
1 => "#bbbbbb",
|
|
2 => "#0088ff",
|
|
3 => "#ffff00",
|
|
4 => "#ff0000",
|
|
5 => "#bb0000",
|
|
_ => "#ff00ff",
|
|
};
|
|
var l = string.Format(
|
|
"\n<color={1}bb><{2}> {3}</color>",
|
|
DateTime.UtcNow.ToString("s"), color, module, msg
|
|
);
|
|
logEntries.Add(l);
|
|
logsLength += l.Length;
|
|
}
|
|
void LogUpdate() {
|
|
logsbuf.Clear();
|
|
loggerListener.Enumerate(d_addLogEntry);
|
|
while (logsLength >= 4096) {
|
|
logsLength -= logEntries[0].Length;
|
|
logEntries.RemoveAt(0);
|
|
}
|
|
foreach (var l in logEntries) {
|
|
logsbuf.Append(l);
|
|
}
|
|
var lbuf = logBufferPool.Rent(logsbuf.Count);
|
|
logsbuf.CopyTo(0, lbuf, 0, logsbuf.Count);
|
|
logs.SetText(lbuf, 0, logsbuf.Count);
|
|
logBufferPool.Return(lbuf);
|
|
|
|
statusbuf.Clear();
|
|
statusbuf.AppendFormat(
|
|
"FPS: i{0:0} / s{1:0}\nSMem: {2:N0} / {3:N0}\nIMem: {4:N0} / {5:N0}",
|
|
1 / Time.deltaTime,
|
|
1 / Time.smoothDeltaTime,
|
|
#if UNITY_5_6_OR_NEWER
|
|
UnityEngine.Profiling.Profiler.GetMonoUsedSizeLong(),
|
|
UnityEngine.Profiling.Profiler.GetMonoHeapSizeLong(),
|
|
UnityEngine.Profiling.Profiler.GetTotalAllocatedMemoryLong(),
|
|
UnityEngine.Profiling.Profiler.GetTotalReservedMemoryLong()
|
|
#else
|
|
UnityEngine.Profiling.Profiler.GetMonoUsedSize(),
|
|
UnityEngine.Profiling.Profiler.GetMonoHeapSize(),
|
|
UnityEngine.Profiling.Profiler.GetTotalAllocatedMemory(),
|
|
UnityEngine.Profiling.Profiler.GetTotalReservedMemory()
|
|
#endif
|
|
);
|
|
if (MotionNodePool.Shared != null) {
|
|
statusbuf.AppendFormat(
|
|
"\nPools: RMV {0}, MC {1}, MN {2}",
|
|
RMVPool.Shared.RentedCount,
|
|
MotionCachePool.Shared.RentedCount,
|
|
MotionNodePool.Shared.RentedCount
|
|
);
|
|
}
|
|
if (texLoader != null) statusbuf.AppendFormat("\n(Loading textures) Progress: {0:P}", texLoader.Progress);
|
|
if (loadThread != null) statusbuf.AppendFormat("\n(Loading files) Progress: {0:P}", loadPregress);
|
|
if (prehandler != null) statusbuf.AppendFormat("\n(Prehandling) Progress: {0:P}", prehandler.Progress);
|
|
if (started) {
|
|
statusbuf.AppendFormat(
|
|
"\nStates: c{0} / b{1}",
|
|
cbus.ActiveStateCount, bbus.ActiveStateCount
|
|
);
|
|
var aTime = Game.AudioClient.Position - atime0;
|
|
var iTime = inputProxy.GetTimestampAverage();
|
|
statusbuf.AppendFormat(
|
|
"\nSTime: {0:G9}s {5} {6}\nATime: {1:G9}s ({3:+0.0ms;-0.0ms;0}) {5} {6}\nITime: {2:G9}s ({4:+0.0ms;-0.0ms;0}) {5} {7}",
|
|
cbus.Time, aTime, iTime,
|
|
(aTime - cbus.Time) * 1e3,
|
|
(iTime - cbus.Time) * 1e3,
|
|
forceSyncFrames != 0 ? "(force sync)" : "",
|
|
paused ? "(paused)" : "",
|
|
paused ? "(semi-locked)" : ""
|
|
);
|
|
if (judge != null) {
|
|
statusbuf.Append("\n== Scores ==\n");
|
|
var fullScoreStrLen = judge.GetFullFormattedScoreString(logBufferPool, out char[] fullScoreStr);
|
|
statusbuf.Append(fullScoreStr, 0, fullScoreStrLen);
|
|
logBufferPool.Return(fullScoreStr);
|
|
}
|
|
}
|
|
var buf = logBufferPool.Rent(statusbuf.Count);
|
|
statusbuf.CopyTo(0, buf, 0, statusbuf.Count);
|
|
status.SetText(buf, 0, statusbuf.Count);
|
|
logBufferPool.Return(buf);
|
|
}
|
|
#endregion
|
|
|
|
#region Triggers
|
|
private void ReturnToMenu() {
|
|
#if UNITY_EDITOR
|
|
Invoke(nameof(ReturnToMenuImpl), 4);
|
|
#else
|
|
ReturnToMenuImpl();
|
|
#endif
|
|
}
|
|
|
|
private void ReturnToMenuImpl() {
|
|
Master.Instance.ShowMenu();
|
|
Destroy(gameObject);
|
|
Game.ResumeBackgroundTasks();
|
|
#if UNITY_5_5_OR_NEWER
|
|
SceneManager.UnloadSceneAsync("Play");
|
|
#elif UNITY_5_3_OR_NEWER
|
|
SceneManager.UnloadScene("Play");
|
|
#endif
|
|
#if UNITY_STANDALONE_WIN || UNITY_EDITOR_WIN
|
|
DiscordController.Instance.SetIdle();
|
|
#endif
|
|
}
|
|
|
|
bool logEnabled = true;
|
|
public void ToggleLogs() {
|
|
logEntries.Clear();
|
|
logsLength = 0;
|
|
logs.text = "";
|
|
status.SetText("");
|
|
logEnabled = !logEnabled;
|
|
}
|
|
|
|
public void TogglePlay() {
|
|
if (started) Stop();
|
|
else if (prehandler != null) {
|
|
prehandler = null;
|
|
Stop();
|
|
}
|
|
else if (texLoader != null || loadThread != null) {
|
|
texloaddone = true;
|
|
texLoader = null;
|
|
if (loadThread.IsAlive) {
|
|
Game.MainLogger.Log(2, "Game", "Stop requested while the chart is loading. Waiting for the loading thread to exit...");
|
|
}
|
|
}
|
|
else {
|
|
Play();
|
|
}
|
|
}
|
|
|
|
bool paused = false;
|
|
public void TogglePause() {
|
|
paused = !paused;
|
|
if (!paused) {
|
|
forceSyncFrames = Settings.Default.ForceSyncFrames;
|
|
Game.AudioClient.Start();
|
|
inputProxy.UnlockTime();
|
|
#if !UNITY_ANDROID || UNITY_EDITOR
|
|
DiscordController.Instance.SetResume(cbus.Time);
|
|
#endif
|
|
}
|
|
else {
|
|
Game.AudioClient.Pause();
|
|
inputProxy.LockTime();
|
|
#if !UNITY_ANDROID || UNITY_EDITOR
|
|
DiscordController.Instance.SetPaused();
|
|
#endif
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Load
|
|
void Play() {
|
|
disableGC = Settings.Default.DisableGC;
|
|
clippingDist = Settings.Default.BackwardRenderDistance;
|
|
renderDist = Settings.Default.ForwardRenderDistance;
|
|
renderStep = Settings.Default.RenderStep;
|
|
actualRenderStep = renderStep;
|
|
autoRenderStep = renderStep == 0;
|
|
graphicalOffset = Settings.Default.GraphicalOffset;
|
|
soundOffset = Settings.Default.SoundOffset;
|
|
startOffset = Settings.Default.StartOffset;
|
|
areaJudgePrecision = 1 << Settings.Default.AreaJudgePrecision;
|
|
forceSyncFrames = Settings.Default.ForceSyncFrames;
|
|
texloaddone = false;
|
|
Game.AudioSession = Game.AudioSequencer.NewSession();
|
|
|
|
var hitPlane = new Plane(Vector3.forward, Vector3.zero);
|
|
var r0 = Camera.main.ViewportPointToRay(new Vector3(0, 0, 1));
|
|
hitPlane.Raycast(r0, out float dist);
|
|
var p0 = r0.GetPoint(dist);
|
|
var r1 = Camera.main.ViewportPointToRay(new Vector3(1, 1, 1));
|
|
hitPlane.Raycast(r1, out dist);
|
|
var p1 = r1.GetPoint(dist);
|
|
hitRect = new Rect(p0, p1 - p0);
|
|
|
|
screenSize = new Vector2(Screen.width, Screen.height);
|
|
frustumPlanes = GeometryUtility.CalculateFrustumPlanes(Camera.main);
|
|
|
|
FileInfo chartFile = new(Settings.Default.LoadChart);
|
|
|
|
FileInfo rulesetFile = new(Path.Combine(
|
|
Game.GameDataPath, "rulesets", Settings.Default.LoadRuleset
|
|
));
|
|
if (!rulesetFile.Exists) throw new FileNotFoundException("Ruleset for the chart not found\nMake sure you have imported the ruleset");
|
|
|
|
FileInfo rulesetConfigFile = new(Path.Combine(
|
|
Game.GameDataPath, "config", "rulesets", Settings.Default.LoadRulesetConfig
|
|
));
|
|
if (!rulesetConfigFile.Exists) throw new FileNotFoundException("Ruleset config not found\nPlease open the config to generate");
|
|
using (StreamReader cfgreader = new(rulesetConfigFile.FullName, Encoding.UTF8)) {
|
|
_rscfg = JsonConvert.DeserializeObject<RulesetConfig>(cfgreader.ReadToEnd(), new JsonSerializerSettings() {
|
|
MissingMemberHandling = MissingMemberHandling.Error
|
|
});
|
|
}
|
|
sv = _rscfg.generic.ScrollVelocity;
|
|
soundOffset += _rscfg.generic.SoundOffset;
|
|
|
|
FileInfo skinFile = new(Path.Combine(
|
|
Game.GameDataPath, "skins", rulesetFile.Directory.Name, _rscfg.generic.Skin, ".umgs"
|
|
));
|
|
if (!skinFile.Exists) throw new FileNotFoundException("Skin not found\nPlease specify an available skin in the config");
|
|
using (StreamReader reader = new(skinFile.FullName, Encoding.UTF8)) {
|
|
skin = JsonConvert.DeserializeObject<SkinDefinition>(reader.ReadToEnd(), new JsonSerializerSettings() {
|
|
MissingMemberHandling = MissingMemberHandling.Error
|
|
});
|
|
if (skin.format != SkinDefinition.CURRENT_FORMAT) throw new FormatException("Invalid skin file version");
|
|
}
|
|
|
|
loadThread = new Thread(new ParameterizedThreadStart(Load));
|
|
loadThread.Start(new LoadInfo() {
|
|
chartFile = chartFile,
|
|
rulesetFile = rulesetFile,
|
|
skinFile = skinFile,
|
|
});
|
|
|
|
Game.MainLogger.Log(0, "Load/MainThread", "Loading textures...");
|
|
frames = new Dictionary<string, SpriteFrame>();
|
|
texs = new Dictionary<string, Texture2D>();
|
|
var skinDir = skinFile.Directory.FullName;
|
|
var texLoadQueue = new List<string>();
|
|
foreach (var f in skin.frames) {
|
|
texLoadQueue.Add(Path.Combine(skinDir, f));
|
|
}
|
|
texLoader = new Coroutine(LoadTextures(texLoadQueue));
|
|
}
|
|
|
|
IEnumerator<float> LoadTextures(List<string> queue) {
|
|
Stopwatch stopwatch = new();
|
|
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(PlatformConfig.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
|
|
string url = texLoader.url;
|
|
string name = StringUtils.TrimExt(url.Substring(url.LastIndexOfAny(new char[] {'/', '\\'}) + 1));
|
|
if (texHandler.isDone) {
|
|
var tex = texHandler.texture;
|
|
tex.wrapMode = TextureWrapMode.Clamp;
|
|
if (frames.ContainsKey(name)) {
|
|
Game.MainLogger.Log(3, "Load/Prehandle", "Duplicated texture name: {0}", name);
|
|
}
|
|
else {
|
|
frames.Add(name, new SpriteFrame(tex));
|
|
}
|
|
texs.Add(name, tex);
|
|
}
|
|
else {
|
|
Game.MainLogger.Log(4, "Load/Prehandle", "Unable to load texture: {0}", name);
|
|
}
|
|
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
|
|
}
|
|
texloaddone = true;
|
|
stopwatch.Stop();
|
|
Game.MainLogger.Log(1, "Load/MainThread", "Main thread done ({0}ms)", stopwatch.Elapsed.TotalMilliseconds);
|
|
yield return 1;
|
|
}
|
|
|
|
IEnumerator<float> Prehandle() {
|
|
Stopwatch timer = new();
|
|
timer.Reset(); timer.Start();
|
|
Game.MainLogger.Log(0, "Load/Prehandle", "Prehandling (iteration 2)"); yield return 0;
|
|
cbus.BroadcastPreInit();
|
|
Game.MainLogger.Log(0, "Load/Prehandle", "Prehandling (iteration 3)"); yield return 0;
|
|
using (var pbus = cbus.Clone(17)) {
|
|
while (pbus.Time != double.PositiveInfinity) {
|
|
pbus.ForwardOnce();
|
|
yield return (float)pbus.EventId / pbus.EventCount;
|
|
}
|
|
}
|
|
Game.MainLogger.Log(0, "Load/Prehandle", "Prehandling (iteration 4)"); yield return 1;
|
|
cbus.BroadcastPostInit();
|
|
Game.MainLogger.Log(0, "Load/Prehandle", "Seeking to start offset"); yield return 1;
|
|
cbus.ForwardByTime(startOffset);
|
|
bbus.ForwardByTime(startOffset);
|
|
Game.AudioSequencer.SeekTime(startOffset, SeekOrigin.Current);
|
|
Game.MainLogger.Log(0, "Load/Prehandle", "Cleaning up"); yield return 1;
|
|
if (logEnabled && Settings.Default.HideLogOnPlay) ToggleLogs();
|
|
Camera.main.cullingMask |= 1;
|
|
GC.Collect();
|
|
if (disableGC) GarbageCollector.GCMode = GarbageCollector.Mode.Disabled;
|
|
timer.Stop();
|
|
Game.MainLogger.Log(1, "Load/Prehandle", "Prehandling done ({0}ms)", timer.Elapsed.TotalMilliseconds); yield return 1;
|
|
if (Settings.Default.ClearLogOnPlay) {
|
|
logEntries.Clear();
|
|
logsLength = 0;
|
|
loggerListener.Enumerate((level, module, msg) => { });
|
|
logs.text = "";
|
|
}
|
|
Game.AudioSequencer.Playing = true;
|
|
atime0 = Game.AudioClient.BufferPosition - startOffset;
|
|
inputProxy.SyncTime(cbus.Time);
|
|
inputProxy.Activate();
|
|
}
|
|
|
|
public void Stop() {
|
|
try {
|
|
Game.MainLogger.Log(1, "Game", "Stopping");
|
|
Game.AudioClient.Start();
|
|
Game.AudioSession = Game.AudioSequencer.NewSession();
|
|
Camera.main.cullingMask &= ~1;
|
|
if (inputProxy != null) {
|
|
inputProxy.Deactivate();
|
|
inputProxy = null;
|
|
}
|
|
judge = null;
|
|
if (nbus != null) { nbus.Dispose(); nbus = null; }
|
|
if (tbus != null) { tbus.Dispose(); tbus = null; }
|
|
if (bbus != null) { bbus.Dispose(); bbus = null; }
|
|
if (cbus != null) { cbus.Dispose(); cbus.DisposeAll(); cbus = null; }
|
|
if (effectManager != null) {
|
|
effectManager.Dispose();
|
|
effectManager = null;
|
|
}
|
|
PdtEvaluator.Instance.Reset();
|
|
motionRegistry = null;
|
|
Game.MainLogger.Log(1, "Game", "Stopped");
|
|
}
|
|
catch (Exception ex) {
|
|
if (!logEnabled) ToggleLogs();
|
|
Game.LogException("Game", "An error occurred while stopping", ex);
|
|
Popup.CreateException(ex);
|
|
}
|
|
finally {
|
|
if (started) {
|
|
if (disableGC) GarbageCollector.GCMode = GarbageCollector.Mode.Enabled;
|
|
GC.Collect();
|
|
started = false;
|
|
}
|
|
}
|
|
ReturnToMenu();
|
|
}
|
|
|
|
void OnSettingsUpdate() {
|
|
Application.targetFrameRate = Settings.Default.TargetFrameRate;
|
|
QualitySettings.vSyncCount = Settings.Default.VSync ? 1 : 0;
|
|
}
|
|
#endregion
|
|
|
|
#region Threaded
|
|
struct LoadInfo {
|
|
public FileInfo chartFile;
|
|
public FileInfo rulesetFile;
|
|
public FileInfo skinFile;
|
|
}
|
|
|
|
Exception threadException;
|
|
Thread loadThread = null;
|
|
volatile float loadPregress;
|
|
Stopwatch workerTimer;
|
|
void Load(object _info) {
|
|
var info = (LoadInfo)_info;
|
|
try {
|
|
workerTimer = new Stopwatch();
|
|
workerTimer.Start();
|
|
LoadChart(info);
|
|
workerTimer.Stop();
|
|
Game.MainLogger.Log(1, "Load/WorkerThread", "Worker thread done ({0}ms)", workerTimer.Elapsed.TotalMilliseconds);
|
|
}
|
|
catch (Exception ex) {
|
|
Game.LogException("Load/WorkerThread", "An error occurred while loading the data", ex);
|
|
threadException = ex;
|
|
}
|
|
}
|
|
|
|
void LoadChart(LoadInfo info) {
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Loading chart: {0}", info.chartFile);
|
|
|
|
motionRegistry = new Dictionary<Identifier, MotionRegistry> {
|
|
{ new Identifier("pt") , new MotionRegistry(typeof(Vec2)) },
|
|
{ new Identifier("dir") , new MotionRegistry(typeof(Vec3)) },
|
|
{ new Identifier("normal") , new MotionRegistry(typeof(Vec3)) },
|
|
{ new Identifier("sv") , new MotionRegistry(new Vec1(0f), new Vec1(hitRect.height)) },
|
|
{ new Identifier("svm") , new MotionRegistry(new Vec1m(1f)) },
|
|
{ new Identifier("dist") , new MotionRegistry(new Vec1(0f), new Vec1(float.PositiveInfinity)) },
|
|
{ new Identifier("track") , new MotionRegistry(typeof(Vec1)) },
|
|
};
|
|
|
|
using StreamReader reader = new(info.chartFile.FullName, Encoding.UTF8);
|
|
PdtEvaluator.Instance.Reset();
|
|
|
|
LoadRuleset(info.rulesetFile); loadPregress = .05f;
|
|
|
|
chart = JsonConvert.DeserializeObject<Chart>(reader.ReadToEnd(), new JsonSerializerSettings() {
|
|
MissingMemberHandling = MissingMemberHandling.Error
|
|
});
|
|
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Applying ruleset (iteration 1)"); loadPregress = .10f;
|
|
pruleset.PrePatch(chart);
|
|
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Batching events"); loadPregress = .20f;
|
|
var batcher = new EventBatcher(chart);
|
|
batcher.Forward();
|
|
cbus = batcher.Batch(); loadPregress = .30f;
|
|
|
|
LoadSkin(info.skinFile);
|
|
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Initializing judge and input"); loadPregress = .35f;
|
|
judge = new Judge(this, pruleset);
|
|
PdtEvaluator.Instance.ContextJudge = judge;
|
|
|
|
inputProxy = new InputProxy(pruleset, judge, screenSize);
|
|
inputProxy.LoadFrom(_rscfg.inputs);
|
|
if (!inputProxy.IsCompleted()) {
|
|
Game.MainLogger.Log(2, "Game", "Input config not completed. Input disabled");
|
|
inputProxy.Clear();
|
|
}
|
|
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Attaching handlers"); loadPregress = .40f;
|
|
var ch = new ChartHandler(chart);
|
|
cbus.RootState.AttachHandler(ch);
|
|
foreach (var gs in cbus.RootState.Children) {
|
|
var gh = new GroupHandler((Chart.Group)gs.Key, ch);
|
|
gs.Value.AttachHandler(gh);
|
|
foreach (var ts in gs.Value.Children) {
|
|
ContainerHandler th;
|
|
if (ts.Key is Chart.Note) {
|
|
th = new NoteHandler((Chart.Note)ts.Key, gh);
|
|
}
|
|
else {
|
|
th = new TrackHandler((Chart.Track)ts.Key, gh);
|
|
}
|
|
ts.Value.AttachHandler(th);
|
|
}
|
|
}
|
|
cbus.AttachSystems(pskin, judge);
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Prehandling (iteration 1)"); loadPregress = .60f;
|
|
using (var pbus = cbus.Clone(16)) {
|
|
pbus.Forward();
|
|
}
|
|
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Cloning states (type 1)"); loadPregress = .70f;
|
|
bbus = cbus.Clone(1, -clippingDist);
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Cloning states (type 2)"); loadPregress = .80f;
|
|
tbus = bbus.Clone(2);
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Cloning states (type 3)"); loadPregress = .90f;
|
|
nbus = bbus.Clone(3);
|
|
loadPregress = 1;
|
|
}
|
|
|
|
void LoadRuleset(FileInfo file) {
|
|
DirectoryInfo dir = file.Directory;
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Loading ruleset: {0}", file);
|
|
using (StreamReader reader = new(file.FullName, Encoding.UTF8)) {
|
|
ruleset = JsonConvert.DeserializeObject<RulesetDefinition>(reader.ReadToEnd(), new JsonSerializerSettings() {
|
|
MissingMemberHandling = MissingMemberHandling.Error
|
|
});
|
|
if (ruleset.format != RulesetDefinition.CURRENT_FORMAT) throw new FormatException("Invalid ruleset file version");
|
|
ruleset.LoadPdt(dir);
|
|
pruleset = ruleset.Root;
|
|
pruleset.Optimize(PdtEvaluator.Instance);
|
|
}
|
|
PdtEvaluator.Instance.ContextRulesetConfig = new RulesetConfigStore(pruleset.configs, _rscfg.configs);
|
|
RMVPool.Shared = new RMVPool();
|
|
MotionCachePool.Shared = new MotionCachePool();
|
|
MotionNodePool.Shared = new MotionNodePool();
|
|
}
|
|
|
|
void LoadSkin(FileInfo file) {
|
|
DirectoryInfo dir = file.Directory;
|
|
Game.MainLogger.Log(0, "Load/WorkerThread", "Loading skin: {0}", file);
|
|
skin.LoadPdt(dir);
|
|
pskin = skin.Root;
|
|
pskin.Optimize(PdtEvaluator.Instance);
|
|
effectManager = new EffectManager(pskin);
|
|
}
|
|
#endregion
|
|
}
|
|
}
|