From d4b9e4b89c8c3db604b670b8792487f2c658f695b6a0f31d90cbda8bcc996e56 Mon Sep 17 00:00:00 2001 From: copecone Date: Thu, 28 May 2026 13:12:42 +0900 Subject: [PATCH] Improve `RenderManager` Object Structure --- Component/RenderManager.cs | 98 ++++++++++++----------- Patch/ClassPatch/scrPlayerManagerPatch.cs | 2 +- Patch/ClassPatch/scrPressToStartPatch.cs | 4 +- Patch/ClassPatch/scrShowIfDebugPatch.cs | 17 ++-- Patch/RenderEnvironmentPatches.cs | 40 ++++----- Patch/ShowHitTextPatch.cs | 2 +- RenderMod.cs | 29 ++++--- UI/DebugMonitorUI.cs | 11 ++- UI/RenderButton.cs | 30 ++++--- Util/FrameReader.cs | 5 +- 10 files changed, 130 insertions(+), 108 deletions(-) diff --git a/Component/RenderManager.cs b/Component/RenderManager.cs index 42203df..fa00389 100644 --- a/Component/RenderManager.cs +++ b/Component/RenderManager.cs @@ -9,23 +9,22 @@ using ADOFAIRenderer.Util.FFmpeg; using MelonLoader; using MelonLoader.Utils; using UnityEngine; +using UnityEngine.Serialization; namespace ADOFAIRenderer.Component { public class RenderManager : MonoBehaviour { - public static int Countdown; - public static long FrameCount; - public static bool SkipPressToStart; - public static string LevelPath = ""; - public static RenderManager Instance = null!; - private static int _originalInputOffset; - private static GameObject? _debugMonitorObj; - private static GameObject? _escapeHandlerObj; - private static string? _currentOutPath; + // Singleton Accessors + public static RenderManager Instance { get; private set; } = null!; - public static bool IsRenderCancelled = false; - public static RenderSettings Configuration; + // Core Render Configuration and Path (OOP Instance Fields) + public int countdown; + public long frameCount; + public bool skipPressToStart; + public string levelPath = ""; + public bool isRenderCancelled; + public RenderSettings Configuration; public struct RenderSettings { @@ -55,14 +54,20 @@ namespace ADOFAIRenderer.Component } } - public static SortedList ImageQueue = new SortedList(); - public static Thread? RenderThread; - public static long PassCount; - public static bool EndRequested; - public static bool EndSuccessful; - public static long WonFrame = -1; + // Thread and Queue State (OOP Instance Fields) + public SortedList ImageQueue = new SortedList(); + public Thread? RenderThread; + public long passCount; + public bool endRequested; + public bool endSuccessful; + public long wonFrame = -1; - private static bool _renderSetup; + // Private Instance Fields + private int _originalInputOffset; + private GameObject? _debugMonitorObj; + private GameObject? _escapeHandlerObj; + private string? _currentOutPath; + private bool _renderSetup; private void Awake() { @@ -101,7 +106,8 @@ namespace ADOFAIRenderer.Component RenderMod.FrameCountBase = Time.frameCount; RenderMod.IsRendering = true; - Countdown = 3; WonFrame = -1; + countdown = 3; + wonFrame = -1; if (!_escapeHandlerObj) { @@ -123,14 +129,14 @@ namespace ADOFAIRenderer.Component } else if (scrController.instance.state == States.Won) { - if (WonFrame == -1) + if (wonFrame == -1) { - WonFrame = FrameCount; + wonFrame = frameCount; } var extraFrames = (long)Math.Ceiling(Configuration.EndWaitTime * Configuration.TargetFPS); - if (FrameCount - WonFrame < extraFrames) return; + if (frameCount - wonFrame < extraFrames) return; RenderMod.IsRendering = RenderMod.IsCapturing = false; StopFFmpeg(true); } @@ -147,25 +153,25 @@ namespace ADOFAIRenderer.Component public void SetupGameScene() { - GCS.customLevelPaths = new[] { LevelPath }; + GCS.customLevelPaths = new[] { levelPath }; GCS.checkpointNum = 0; GCS.customLevelId = ""; GCS.nextSpeedRun = 1; GCS.customLevelIndex = 0; ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnGame"); - SkipPressToStart = true; + skipPressToStart = true; _renderSetup = true; } private IEnumerator SetupPlay() { - for (; Countdown > 0; Countdown--) + for (; countdown > 0; countdown--) { yield return new WaitForSecondsRealtime(1); } - FrameCount = 0; + frameCount = 0; RenderMod.FrameReader?.Dispose(); RenderMod.FrameReader = new FrameReader(); @@ -179,7 +185,7 @@ namespace ADOFAIRenderer.Component scrConductor.instance.song.time = -1000; } - private static string FindFFmpegPath() + private string FindFFmpegPath() { var isWindows = Application.platform == RuntimePlatform.WindowsPlayer; var ffmpegName = isWindows ? "ffmpeg.exe" : "ffmpeg"; @@ -218,16 +224,16 @@ namespace ADOFAIRenderer.Component { ImageQueue.Clear(); } - PassCount = 0; - IsRenderCancelled = false; + passCount = 0; + isRenderCancelled = false; RenderMod.IsRenderCancelled = false; RenderMod.FrameCountForVideo = 0; RenderMod.LastVideoFrameIndex = -1; RenderMod.LastRequestedVideoFrameIndex = -1; RenderMod.RenderCheck = true; RenderMod.SkipCount = 0; - EndRequested = false; - EndSuccessful = false; + endRequested = false; + endSuccessful = false; var targetFps = Configuration.TargetFPS; var videoFps = Configuration.VideoFPS; @@ -274,16 +280,16 @@ namespace ADOFAIRenderer.Component { var firstKey = ImageQueue.Keys[0]; - if (firstKey > PassCount && !RenderMod.IsCapturing) + if (firstKey > passCount && !RenderMod.IsCapturing) { - PassCount = firstKey; + passCount = firstKey; } - if (firstKey == PassCount) + if (firstKey == passCount) { buffer = ImageQueue[firstKey]; ImageQueue.RemoveAt(0); - PassCount++; + passCount++; } } } @@ -299,14 +305,14 @@ namespace ADOFAIRenderer.Component continue; } - if (IsRenderCancelled || RenderMod.IsRenderCancelled) + if (isRenderCancelled || RenderMod.IsRenderCancelled) { break; } - if (EndRequested) + if (endRequested) { - if (PassCount > RenderMod.LastRequestedVideoFrameIndex) + if (passCount > RenderMod.LastRequestedVideoFrameIndex) { break; } @@ -316,7 +322,7 @@ namespace ADOFAIRenderer.Component } videoFFmpeg.Close(); - EndSuccessful = true; + endSuccessful = true; } catch (Exception ex) { @@ -330,7 +336,7 @@ namespace ADOFAIRenderer.Component public void CancelRender() { RenderMod.IsRendering = RenderMod.IsCapturing = false; - IsRenderCancelled = true; + isRenderCancelled = true; RenderMod.IsRenderCancelled = true; StopFFmpeg(false); @@ -339,13 +345,13 @@ namespace ADOFAIRenderer.Component private void StopFFmpeg(bool success) { - EndRequested = true; + endRequested = true; if (!success) { - IsRenderCancelled = true; + isRenderCancelled = true; RenderMod.IsRenderCancelled = true; } - SkipPressToStart = false; + skipPressToStart = false; _renderSetup = false; if (_escapeHandlerObj) { @@ -422,13 +428,13 @@ namespace ADOFAIRenderer.Component } } - private static IEnumerator StopRenderCoroutine() + private IEnumerator StopRenderCoroutine() { - yield return new WaitUntil(() => EndSuccessful); + yield return new WaitUntil(() => endSuccessful); EndRender(); } - private static void EndRender() + private void EndRender() { Time.timeScale = 1f; Time.captureDeltaTime = 0f; diff --git a/Patch/ClassPatch/scrPlayerManagerPatch.cs b/Patch/ClassPatch/scrPlayerManagerPatch.cs index 79254b3..6eaba79 100644 --- a/Patch/ClassPatch/scrPlayerManagerPatch.cs +++ b/Patch/ClassPatch/scrPlayerManagerPatch.cs @@ -14,7 +14,7 @@ namespace ADOFAIRenderer.Patch.ClassPatch { if (!RenderMod.IsRendering) return true; - __result = RenderManager.FrameCount == 0 && RenderManager.Countdown == 0; + __result = RenderManager.Instance && RenderManager.Instance.frameCount == 0 && RenderManager.Instance.countdown == 0; return false; } } diff --git a/Patch/ClassPatch/scrPressToStartPatch.cs b/Patch/ClassPatch/scrPressToStartPatch.cs index e13dad0..c8b321a 100644 --- a/Patch/ClassPatch/scrPressToStartPatch.cs +++ b/Patch/ClassPatch/scrPressToStartPatch.cs @@ -11,9 +11,9 @@ namespace ADOFAIRenderer.Patch.ClassPatch [HarmonyPatch("ShowText")] private static bool PreventShowText() { - if (!RenderManager.SkipPressToStart) return true; + if (!RenderManager.Instance || !RenderManager.Instance.skipPressToStart) return true; - RenderManager.SkipPressToStart = false; + RenderManager.Instance.skipPressToStart = false; return false; } } diff --git a/Patch/ClassPatch/scrShowIfDebugPatch.cs b/Patch/ClassPatch/scrShowIfDebugPatch.cs index 1c10b5c..9916f7a 100644 --- a/Patch/ClassPatch/scrShowIfDebugPatch.cs +++ b/Patch/ClassPatch/scrShowIfDebugPatch.cs @@ -18,14 +18,17 @@ namespace ADOFAIRenderer.Patch.ClassPatch if (!RenderMod.IsRendering) return; ___txt.text = "Render"; - if (RenderManager.Countdown > 0) ___txt.text = $"Render in {RenderManager.Countdown}s.."; - - if (RenderManager.WonFrame != -1) + if (RenderManager.Instance) { - var elapsedFrames = RenderManager.FrameCount - RenderManager.WonFrame; - var remainingTime = RenderManager.Configuration.EndWaitTime - elapsedFrames / RenderManager.Configuration.TargetFPS; - if (remainingTime < 0) remainingTime = 0; - ___txt.text += $"\nWaiting for {remainingTime:F1}s.."; + if (RenderManager.Instance.countdown > 0) ___txt.text = $"Render in {RenderManager.Instance.countdown}s.."; + + if (RenderManager.Instance.wonFrame != -1) + { + var elapsedFrames = RenderManager.Instance.frameCount - RenderManager.Instance.wonFrame; + var remainingTime = RenderManager.Instance.Configuration.EndWaitTime - elapsedFrames / RenderManager.Instance.Configuration.TargetFPS; + if (remainingTime < 0) remainingTime = 0; + ___txt.text += $"\nWaiting for {remainingTime:F1}s.."; + } } ___txt.color = ___defaultColor; diff --git a/Patch/RenderEnvironmentPatches.cs b/Patch/RenderEnvironmentPatches.cs index a669813..0664485 100644 --- a/Patch/RenderEnvironmentPatches.cs +++ b/Patch/RenderEnvironmentPatches.cs @@ -15,8 +15,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref float __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = (float)(RenderManager.FrameCount / RenderManager.Configuration.TargetFPS + RenderMod.TimeBase); + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.TimeBase); return false; } } @@ -27,8 +27,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref double __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = RenderManager.FrameCount / RenderManager.Configuration.TargetFPS + RenderMod.TimeBase; + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.TimeBase; return false; } } @@ -39,8 +39,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref float __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = (float)(RenderManager.FrameCount / RenderManager.Configuration.TargetFPS + RenderMod.UnscaledTimeBase); + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = (float)(RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.UnscaledTimeBase); return false; } } @@ -51,8 +51,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref double __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = RenderManager.FrameCount / RenderManager.Configuration.TargetFPS + RenderMod.UnscaledTimeBase; + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.UnscaledTimeBase; return false; } } @@ -63,8 +63,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref double __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = RenderManager.FrameCount / RenderManager.Configuration.TargetFPS + RenderMod.DspTimeBase; + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = RenderManager.Instance.frameCount / RenderManager.Instance.Configuration.TargetFPS + RenderMod.DspTimeBase; return false; } @@ -86,8 +86,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref int __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = (int)(RenderManager.FrameCount + RenderMod.FrameCountBase); + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = (int)(RenderManager.Instance.frameCount + RenderMod.FrameCountBase); return false; } } @@ -98,8 +98,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref float __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = (float)(1 / RenderManager.Configuration.TargetFPS); + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = (float)(1 / RenderManager.Instance.Configuration.TargetFPS); return false; } } @@ -110,8 +110,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref float __result) { - if (!RenderMod.IsCapturing || !RenderPatch) return true; - __result = (float)(1 / RenderManager.Configuration.TargetFPS); + if (!RenderMod.IsCapturing || !RenderPatch || !RenderManager.Instance) return true; + __result = (float)(1 / RenderManager.Instance.Configuration.TargetFPS); return false; } } @@ -122,8 +122,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref int __result) { - if (!RenderMod.IsRendering || !RenderPatch) return true; - __result = RenderManager.Configuration.RenderWidth; + if (!RenderMod.IsRendering || !RenderPatch || !RenderManager.Instance) return true; + __result = RenderManager.Instance.Configuration.RenderWidth; return false; } } @@ -134,8 +134,8 @@ namespace ADOFAIRenderer.Patch [SuppressMessage("ReSharper", "InconsistentNaming")] public static bool Prefix(ref int __result) { - if (!RenderMod.IsRendering || !RenderPatch) return true; - __result = RenderManager.Configuration.RenderHeight; + if (!RenderMod.IsRendering || !RenderPatch || !RenderManager.Instance) return true; + __result = RenderManager.Instance.Configuration.RenderHeight; return false; } } diff --git a/Patch/ShowHitTextPatch.cs b/Patch/ShowHitTextPatch.cs index b2717d9..46f2859 100644 --- a/Patch/ShowHitTextPatch.cs +++ b/Patch/ShowHitTextPatch.cs @@ -8,7 +8,7 @@ namespace ADOFAIRenderer.Patch { public static bool Prefix() { - return !RenderMod.IsRendering || RenderManager.Configuration.ShowHitText; + return !RenderMod.IsRendering || (RenderManager.Instance && RenderManager.Instance.Configuration.ShowHitText); } } } \ No newline at end of file diff --git a/RenderMod.cs b/RenderMod.cs index 5d1087c..dc16404 100644 --- a/RenderMod.cs +++ b/RenderMod.cs @@ -86,18 +86,18 @@ namespace ADOFAIRenderer { if (SkipCount < 60) { - Melon.Logger.Warning($"[Render Skipped] Frame {RenderManager.FrameCount} rendering missed. Retrying... (Attempt {SkipCount + 1}/60)"); + Melon.Logger.Warning($"[Render Skipped] Frame {RenderManager.Instance.frameCount} rendering missed. Retrying... (Attempt {SkipCount + 1}/60)"); SkipCount++; Time.timeScale = 0f; // 진행 시간 멈추기 (재시도) return; // 업데이트 진행하지 않고 대기 } // 60회 초과 시 최후의 블랙 프레임 삽입하여 전체 파이프라인 마비 방지 - Melon.Logger.Error($"[Render Skipped] Frame {RenderManager.FrameCount} rendering failed after 60 retries. Supplying black frame."); + Melon.Logger.Error($"[Render Skipped] Frame {RenderManager.Instance.frameCount} rendering failed after 60 retries. Supplying black frame."); var currentFrame = FrameCountForVideo++; - var targetFps = RenderManager.Configuration.TargetFPS; - var videoFps = RenderManager.Configuration.VideoFPS; + var targetFps = RenderManager.Instance.Configuration.TargetFPS; + var videoFps = RenderManager.Instance.Configuration.VideoFPS; var effectiveVideoFps = Math.Min(videoFps, targetFps); var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps); @@ -106,13 +106,13 @@ namespace ADOFAIRenderer LastVideoFrameIndex = videoFrameIndex; LastRequestedVideoFrameIndex = videoFrameIndex; - var byteSize = RenderManager.Configuration.RenderWidth * RenderManager.Configuration.RenderHeight * 4; + var byteSize = RenderManager.Instance.Configuration.RenderWidth * RenderManager.Instance.Configuration.RenderHeight * 4; var blackFrame = System.Buffers.ArrayPool.Shared.Rent(byteSize); Array.Clear(blackFrame, 0, byteSize); - lock (RenderManager.ImageQueue) + lock (RenderManager.Instance.ImageQueue) { - RenderManager.ImageQueue[videoFrameIndex] = blackFrame; + RenderManager.Instance.ImageQueue[videoFrameIndex] = blackFrame; } } @@ -121,7 +121,7 @@ namespace ADOFAIRenderer SkipCount = 0; RenderCheck = true; // 다음 프레임 검사용으로 초기화 - RenderManager.FrameCount++; + RenderManager.Instance.frameCount++; if (realDeltaTime > 0) { @@ -150,9 +150,9 @@ namespace ADOFAIRenderer while (IsCapturing && IsRendering) { int count; - lock (RenderManager.ImageQueue) + lock (RenderManager.Instance.ImageQueue) { - count = RenderManager.ImageQueue.Count; + count = RenderManager.Instance.ImageQueue.Count; } if (count <= maxQueuedFrames) break; @@ -168,8 +168,8 @@ namespace ADOFAIRenderer var currentFrame = FrameCountForVideo++; - var targetFps = RenderManager.Configuration.TargetFPS; - var videoFps = RenderManager.Configuration.VideoFPS; + var targetFps = RenderManager.Instance.Configuration.TargetFPS; + var videoFps = RenderManager.Instance.Configuration.VideoFPS; var effectiveVideoFps = Math.Min(videoFps, targetFps); var videoFrameIndex = (long)(currentFrame * effectiveVideoFps / targetFps); @@ -227,7 +227,10 @@ namespace ADOFAIRenderer return; } - RenderManager.LevelPath = ADOBase.levelPath; + if (RenderManager.Instance) + { + RenderManager.Instance.levelPath = ADOBase.levelPath; + } scnEditor.instance.CheckUnsavedChanges(() => { diff --git a/UI/DebugMonitorUI.cs b/UI/DebugMonitorUI.cs index 18f3504..727b674 100644 --- a/UI/DebugMonitorUI.cs +++ b/UI/DebugMonitorUI.cs @@ -187,12 +187,15 @@ namespace ADOFAIRenderer.UI var sb = new StringBuilder(); sb.AppendLine("ADOFAI Renderer Realtime Monitor"); sb.AppendLine(); - sb.AppendLine($"• Rendered Progress: {RenderManager.FrameCount} frames"); + sb.AppendLine($"• Rendered Progress: {(RenderManager.Instance ? RenderManager.Instance.frameCount : 0)} frames"); - int queueCount; - lock (RenderManager.ImageQueue) + var queueCount = 0; + if (RenderManager.Instance) { - queueCount = RenderManager.ImageQueue.Count; + lock (RenderManager.Instance.ImageQueue) + { + queueCount = RenderManager.Instance.ImageQueue.Count; + } } sb.AppendLine($"• Queued Buffers: {queueCount} / 60 frames"); diff --git a/UI/RenderButton.cs b/UI/RenderButton.cs index 2b9f4a9..3d1eb8b 100644 --- a/UI/RenderButton.cs +++ b/UI/RenderButton.cs @@ -84,26 +84,32 @@ namespace ADOFAIRenderer.UI return; } - Melon.Logger.Msg($"레벨 로드: {RenderManager.LevelPath}"); + if (RenderManager.Instance) + { + Melon.Logger.Msg($"레벨 로드: {RenderManager.Instance.levelPath}"); + } var selectedCodecIndex = codecDropdown.value; var selectedCodecName = codecDropdown.options[selectedCodecIndex].text; var showHitText = showHitTextToggle != null && showHitTextToggle.isOn; - RenderManager.Configuration = new RenderManager.RenderSettings + if (RenderManager.Instance) { - TargetFPS = targetFPS, - VideoFPS = videoFPS, - RenderWidth = renderWidth, - RenderHeight = renderHeight, - Bitrate = bitrate, - Codec = selectedCodecName, - EndWaitTime = waitAfterEnd, - ShowHitText = showHitText - }; + RenderManager.Instance.Configuration = new RenderManager.RenderSettings + { + TargetFPS = targetFPS, + VideoFPS = videoFPS, + RenderWidth = renderWidth, + RenderHeight = renderHeight, + Bitrate = bitrate, + Codec = selectedCodecName, + EndWaitTime = waitAfterEnd, + ShowHitText = showHitText + }; + } - RenderManager.Instance.SetupCaptureDirectory(); + RenderManager.Instance!.SetupCaptureDirectory(); RenderManager.Instance.SetupGameScene(); } diff --git a/Util/FrameReader.cs b/Util/FrameReader.cs index 3e19f36..b4a094e 100644 --- a/Util/FrameReader.cs +++ b/Util/FrameReader.cs @@ -126,9 +126,10 @@ namespace ADOFAIRenderer.Util private static void PushToQueue(long frame, byte[] rawBytes) { - lock (RenderManager.ImageQueue) + if (!RenderManager.Instance) return; + lock (RenderManager.Instance.ImageQueue) { - RenderManager.ImageQueue[frame] = rawBytes; + RenderManager.Instance.ImageQueue[frame] = rawBytes; } }