diff --git a/Core/RenderManager.cs b/Core/RenderManager.cs index 9bdf07c..e78f74c 100644 --- a/Core/RenderManager.cs +++ b/Core/RenderManager.cs @@ -624,6 +624,22 @@ namespace ADOFAIRenderer.Core GCS.customLevelPaths = null; GCS.customLevelId = null; + lock (ImageQueue) + { + foreach (var kvp in ImageQueue.Values) + { + if (kvp.ManagedBuffer != null && kvp.ManagedBuffer.Length > 0) + { + ArrayPool.Shared.Return(kvp.ManagedBuffer); + } + if (kvp.ReadbackState != null) + { + _frameReader?.RecycleState(kvp.ReadbackState); + } + } + ImageQueue.Clear(); + } + _frameReader?.Dispose(); ADOBase.loader.LoadSceneWithTransition(WipeDirection.StartsFromRight, "scnEditor"); } diff --git a/Util/FrameReader.cs b/Util/FrameReader.cs index e8074d8..9b05308 100644 --- a/Util/FrameReader.cs +++ b/Util/FrameReader.cs @@ -1,8 +1,8 @@ using System; using System.Buffers; using System.Collections.Generic; +using System.Linq; using ADOFAIRenderer.Core; -using ADOFAIRenderer.Util.FFmpeg; using MelonLoader; using Unity.Collections; using Unity.Collections.LowLevel.Unsafe; @@ -16,6 +16,7 @@ namespace ADOFAIRenderer.Util private const int InitialBufferSize = 16; private readonly Stack _statePool = new Stack(); + private readonly List _allocatedStates = new List(); private readonly object _poolLock = new object(); private bool _isInitialized; private volatile bool _isDisposed; @@ -27,13 +28,14 @@ namespace ADOFAIRenderer.Util private void InitializePool() { - lock (_poolLock) { if (_isInitialized) return; for (var i = 0; i < InitialBufferSize; i++) { - _statePool.Push(new ReadbackRequestState(this)); + var state = new ReadbackRequestState(this); + _statePool.Push(state); + _allocatedStates.Add(state); } _isInitialized = true; @@ -47,7 +49,17 @@ namespace ADOFAIRenderer.Util lock (_poolLock) { - state = _statePool.Count == 0 ? new ReadbackRequestState(this) : _statePool.Pop(); + if (_statePool.Count == 0) + { + state = new ReadbackRequestState(this); + _allocatedStates.Add(state); + } + else + { + state = _statePool.Pop(); + } + + state.IsInFlight = true; } var bufferSize = texture.width * texture.height * 4; // RGBA = 4 channel @@ -67,6 +79,7 @@ namespace ADOFAIRenderer.Util lock (_poolLock) { + state.IsInFlight = false; if (_isDisposed) { state.Dispose(); @@ -146,11 +159,12 @@ namespace ADOFAIRenderer.Util { if (_isDisposed) return; _isDisposed = true; - while (_statePool.Count > 0) + foreach (var state in _allocatedStates.Where(state => !state.IsInFlight)) { - var state = _statePool.Pop(); state.Dispose(); } + _statePool.Clear(); + _allocatedStates.Clear(); _isInitialized = false; } } @@ -161,6 +175,7 @@ namespace ADOFAIRenderer.Util private readonly FrameReader _owner; public long FrameIndex { get; private set; } public NativeArray NativeBuffer; + public bool IsInFlight { get; internal set; } public readonly Action CachedCallback; diff --git a/Util/StreamUtil.cs b/Util/StreamUtil.cs index 8d73c04..fc21487 100644 --- a/Util/StreamUtil.cs +++ b/Util/StreamUtil.cs @@ -1,5 +1,6 @@ using System; using System.IO; +using MelonLoader; namespace ADOFAIRenderer.Util { @@ -13,11 +14,15 @@ namespace ADOFAIRenderer.Util unsafe { - // Create an unmanaged memory stream that wraps the native pointer - // (This does not allocate new memory; it references the existing pointer) - using var unmanagedStream = new UnmanagedMemoryStream((byte*)nativePtr, length); - // Copy the data directly into your target stream - unmanagedStream.CopyTo(destinationStream); + try + { + var span = new ReadOnlySpan((void*)nativePtr, length); + destinationStream.Write(span); + } + catch (IOException err) + { + Melon.Logger.Error($"FFmpeg 파이프가 예상치 못하게 닫혔습니다: {err.Message}"); + } } } }