Files
crtr/Assets/Cryville/Crtr/ChartPlayer.cs

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
}
}