SHA256
Add Render Cleanup Logic
This commit is contained in:
1 parent
459b2f3b0e
commit
f4062912a0
3 files changed
+139
-3
No files matched your search
+82
-2
@@ -88,6 +88,8 @@ namespace ADOFAIRenderer.Core
|
|||||||
public long wonFrame = -1;
|
public long wonFrame = -1;
|
||||||
|
|
||||||
// Private Instance Fields
|
// Private Instance Fields
|
||||||
|
private VideoFFmpeg? _activeVideoFFmpeg;
|
||||||
|
private readonly List<VideoFFmpeg> _allVideoFFmpegInstances = new List<VideoFFmpeg>();
|
||||||
private int _originalInputOffset;
|
private int _originalInputOffset;
|
||||||
private GameObject? _debugMonitorObj;
|
private GameObject? _debugMonitorObj;
|
||||||
private GameObject? _escapeHandlerObj;
|
private GameObject? _escapeHandlerObj;
|
||||||
@@ -99,6 +101,49 @@ namespace ADOFAIRenderer.Core
|
|||||||
Instance = this;
|
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()
|
private void Update()
|
||||||
{
|
{
|
||||||
if (_renderSetup && ADOBase.lm)
|
if (_renderSetup && ADOBase.lm)
|
||||||
@@ -405,13 +450,19 @@ namespace ADOFAIRenderer.Core
|
|||||||
|
|
||||||
RenderThread = new Thread(() =>
|
RenderThread = new Thread(() =>
|
||||||
{
|
{
|
||||||
|
VideoFFmpeg? videoFFmpeg = null;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var effectiveVideoFps = Math.Min(videoFps, targetFps);
|
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();
|
SetupCaptureDirectory();
|
||||||
|
|
||||||
|
_activeVideoFFmpeg = videoFFmpeg;
|
||||||
|
lock (_allVideoFFmpegInstances)
|
||||||
|
{
|
||||||
|
_allVideoFFmpegInstances.Add(videoFFmpeg);
|
||||||
|
}
|
||||||
videoFFmpeg.Start(_currentOutPath, bitrate);
|
videoFFmpeg.Start(_currentOutPath, bitrate);
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
@@ -464,12 +515,35 @@ namespace ADOFAIRenderer.Core
|
|||||||
Thread.Sleep(1);
|
Thread.Sleep(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isRenderCancelled)
|
||||||
|
{
|
||||||
|
videoFFmpeg.Kill();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
videoFFmpeg.Close();
|
videoFFmpeg.Close();
|
||||||
endSuccessful = true;
|
}
|
||||||
|
|
||||||
|
lock (_allVideoFFmpegInstances)
|
||||||
|
{
|
||||||
|
_allVideoFFmpegInstances.Remove(videoFFmpeg);
|
||||||
|
}
|
||||||
|
|
||||||
|
_activeVideoFFmpeg = null;
|
||||||
|
endSuccessful = !isRenderCancelled;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Melon<RenderMod>.Logger.Error($"FFmpeg 렌더링 스레드 오류: {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("렌더링이 실패했거나 취소되었습니다. 상태 및 리소스를 안전하게 청소합니다.");
|
Melon<RenderMod>.Logger.Error("렌더링이 실패했거나 취소되었습니다. 상태 및 리소스를 안전하게 청소합니다.");
|
||||||
|
|
||||||
|
if (_activeVideoFFmpeg != null)
|
||||||
|
{
|
||||||
|
_activeVideoFFmpeg.Kill();
|
||||||
|
_activeVideoFFmpeg = null;
|
||||||
|
}
|
||||||
|
|
||||||
// 1. 메모리 누수 방지: 큐에 렌트된 임시 프레임 버퍼 풀 반환 및 클리어
|
// 1. 메모리 누수 방지: 큐에 렌트된 임시 프레임 버퍼 풀 반환 및 클리어
|
||||||
lock (ImageQueue)
|
lock (ImageQueue)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -46,5 +46,12 @@ namespace ADOFAIRenderer
|
|||||||
var newModule = EventSystem.current.gameObject.AddComponent<StandaloneInputModule>();
|
var newModule = EventSystem.current.gameObject.AddComponent<StandaloneInputModule>();
|
||||||
newModule.enabled = true;
|
newModule.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void OnApplicationQuit()
|
||||||
|
{
|
||||||
|
if (RenderManager.Instance == null) return;
|
||||||
|
Melon<RenderMod>.Logger.Msg("MelonLoader 종료 감지: RenderManager의 활성 프로세스를 강제 수거 및 종료합니다.");
|
||||||
|
RenderManager.Instance.CleanUpActiveRender();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ using Unity.Collections;
|
|||||||
|
|
||||||
namespace ADOFAIRenderer.Util.FFmpeg
|
namespace ADOFAIRenderer.Util.FFmpeg
|
||||||
{
|
{
|
||||||
public class FFmpegProcess
|
public class FFmpegProcess : IDisposable
|
||||||
{
|
{
|
||||||
public static string FFmpegPath = "";
|
public static string FFmpegPath = "";
|
||||||
|
|
||||||
@@ -47,10 +47,59 @@ namespace ADOFAIRenderer.Util.FFmpeg
|
|||||||
public void Close()
|
public void Close()
|
||||||
{
|
{
|
||||||
if (_ffmpegProcess == null || _ffmpegProcess.HasExited || _inputStream == null) return;
|
if (_ffmpegProcess == null || _ffmpegProcess.HasExited || _inputStream == null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
_inputStream.Flush();
|
_inputStream.Flush();
|
||||||
_inputStream.Close();
|
_inputStream.Close();
|
||||||
|
|
||||||
|
// Allow FFmpeg to finish encoding and exit gracefully without any timeout to prevent video corruption
|
||||||
_ffmpegProcess.WaitForExit();
|
_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)
|
public static byte[] RentRawBytes(int size)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in new issue
Block a user