Add Render Cleanup Logic

This commit is contained in:
copecone committed 2026-05-30 05:49:28 +09:00
1 parent 459b2f3b0e
commit f4062912a0
3 files changed
+139 -3

No files matched your search

+82 -2
View File
@@ -88,6 +88,8 @@ namespace ADOFAIRenderer.Core
public long wonFrame = -1;
// Private Instance Fields
private VideoFFmpeg? _activeVideoFFmpeg;
private readonly List<VideoFFmpeg> _allVideoFFmpegInstances = new List<VideoFFmpeg>();
private int _originalInputOffset;
private GameObject? _debugMonitorObj;
private GameObject? _escapeHandlerObj;
@@ -99,6 +101,49 @@ namespace ADOFAIRenderer.Core
Instance = this;
}
private void OnDestroy()
{
CleanUpActiveRender();
}
public void CleanUpActiveRender()
{
isRendering = isCapturing = false;
isRenderCancelled = true;
if (_activeVideoFFmpeg != null)
{
Melon<RenderMod>.Logger.Msg("게임 종료 또는 RenderManager 파괴 감지: FFmpeg 렌더 프로세스를 강제 종료합니다.");
_activeVideoFFmpeg.Kill();
_activeVideoFFmpeg = null;
}
lock (_allVideoFFmpegInstances)
{
if (_allVideoFFmpegInstances.Count > 0)
{
Melon<RenderMod>.Logger.Msg($"게임 종료 감지: 생성된 모든 FFmpeg 프로세스({_allVideoFFmpegInstances.Count}개)를 강제 수거 및 종료합니다.");
foreach (var instance in _allVideoFFmpegInstances)
{
try
{
instance.Kill();
}
catch
{
// Ignore
}
}
_allVideoFFmpegInstances.Clear();
}
}
if (RenderThread is { IsAlive: true })
{
RenderThread.Join(200);
}
}
private void Update()
{
if (_renderSetup && ADOBase.lm)
@@ -405,13 +450,19 @@ namespace ADOFAIRenderer.Core
RenderThread = new Thread(() =>
{
VideoFFmpeg? videoFFmpeg = null;
try
{
var effectiveVideoFps = Math.Min(videoFps, targetFps);
var videoFFmpeg = new VideoFFmpeg(Configuration.RenderWidth, Configuration.RenderHeight, effectiveVideoFps, videoFps, selectedCodec);
videoFFmpeg = new VideoFFmpeg(Configuration.RenderWidth, Configuration.RenderHeight, effectiveVideoFps, videoFps, selectedCodec);
SetupCaptureDirectory();
_activeVideoFFmpeg = videoFFmpeg;
lock (_allVideoFFmpegInstances)
{
_allVideoFFmpegInstances.Add(videoFFmpeg);
}
videoFFmpeg.Start(_currentOutPath, bitrate);
while (true)
@@ -464,12 +515,35 @@ namespace ADOFAIRenderer.Core
Thread.Sleep(1);
}
if (isRenderCancelled)
{
videoFFmpeg.Kill();
}
else
{
videoFFmpeg.Close();
endSuccessful = true;
}
lock (_allVideoFFmpegInstances)
{
_allVideoFFmpegInstances.Remove(videoFFmpeg);
}
_activeVideoFFmpeg = null;
endSuccessful = !isRenderCancelled;
}
catch (Exception ex)
{
Melon<RenderMod>.Logger.Error($"FFmpeg 렌더링 스레드 오류: {ex}");
_activeVideoFFmpeg?.Kill();
if (videoFFmpeg != null)
{
lock (_allVideoFFmpegInstances)
{
_allVideoFFmpegInstances.Remove(videoFFmpeg);
}
}
_activeVideoFFmpeg = null;
}
})
{
@@ -511,6 +585,12 @@ namespace ADOFAIRenderer.Core
{
Melon<RenderMod>.Logger.Error("렌더링이 실패했거나 취소되었습니다. 상태 및 리소스를 안전하게 청소합니다.");
if (_activeVideoFFmpeg != null)
{
_activeVideoFFmpeg.Kill();
_activeVideoFFmpeg = null;
}
// 1. 메모리 누수 방지: 큐에 렌트된 임시 프레임 버퍼 풀 반환 및 클리어
lock (ImageQueue)
{
+7
View File
@@ -46,5 +46,12 @@ namespace ADOFAIRenderer
var newModule = EventSystem.current.gameObject.AddComponent<StandaloneInputModule>();
newModule.enabled = true;
}
public override void OnApplicationQuit()
{
if (RenderManager.Instance == null) return;
Melon<RenderMod>.Logger.Msg("MelonLoader 종료 감지: RenderManager의 활성 프로세스를 강제 수거 및 종료합니다.");
RenderManager.Instance.CleanUpActiveRender();
}
}
}
+50 -1
View File
@@ -6,7 +6,7 @@ using Unity.Collections;
namespace ADOFAIRenderer.Util.FFmpeg
{
public class FFmpegProcess
public class FFmpegProcess : IDisposable
{
public static string FFmpegPath = "";
@@ -47,10 +47,59 @@ namespace ADOFAIRenderer.Util.FFmpeg
public void Close()
{
if (_ffmpegProcess == null || _ffmpegProcess.HasExited || _inputStream == null) return;
try
{
_inputStream.Flush();
_inputStream.Close();
// Allow FFmpeg to finish encoding and exit gracefully without any timeout to prevent video corruption
_ffmpegProcess.WaitForExit();
}
catch (Exception)
{
// Ignore any issues during termination
}
finally
{
_ffmpegProcess.Dispose();
_ffmpegProcess = null;
_inputStream = null;
}
}
public void Kill()
{
try
{
if (_ffmpegProcess != null && !_ffmpegProcess.HasExited)
{
_ffmpegProcess.Kill();
}
}
catch (Exception)
{
// Ignore process already exited or access denied errors during cleanup
}
finally
{
try
{
_inputStream?.Close();
}
catch
{
// Ignore
}
_ffmpegProcess?.Dispose();
_ffmpegProcess = null;
_inputStream = null;
}
}
public void Dispose()
{
Kill();
}
public static byte[] RentRawBytes(int size)
{