using System; using System.Collections.Generic; using System.Linq; using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; using Avalonia.Threading; namespace Avalonia.Rendering.Composition.Transport; /// /// A pool that keeps a number of elements that was used in the last 10 seconds /// internal abstract class BatchStreamPoolBase : IDisposable { readonly Stack _pool = new(); bool _disposed; int _usage; readonly int[] _usageStatistics = new int[10]; int _usageStatisticsSlot; public int CurrentUsage => _usage; public int CurrentPool => _pool.Count; public BatchStreamPoolBase(bool needsFinalize, Action>? startTimer = null) { if(!needsFinalize) GC.SuppressFinalize(needsFinalize); var updateRef = new WeakReference>(this); StartUpdateTimer(startTimer, updateRef); } static void StartUpdateTimer(Action>? startTimer, WeakReference> updateRef) { Func timerProc = () => { if (updateRef.TryGetTarget(out var target)) { target.UpdateStatistics(); return true; } return false; }; if (startTimer != null) startTimer(timerProc); else DispatcherTimer.Run(timerProc, TimeSpan.FromSeconds(1)); } private void UpdateStatistics() { lock (_pool) { var maximumUsage = _usageStatistics.Max(); var recentlyUsedPooledSlots = maximumUsage - _usage; var keepSlots = Math.Max(recentlyUsedPooledSlots, 10); while (keepSlots < _pool.Count) DestroyItem(_pool.Pop()); _usageStatisticsSlot = (_usageStatisticsSlot + 1) % _usageStatistics.Length; _usageStatistics[_usageStatisticsSlot] = 0; } } protected abstract T CreateItem(); protected virtual void DestroyItem(T item) { } public T Get() { lock (_pool) { _usage++; if (_usageStatistics[_usageStatisticsSlot] < _usage) _usageStatistics[_usageStatisticsSlot] = _usage; if (_pool.Count != 0) return _pool.Pop(); } return CreateItem(); } public void Return(T item) { lock (_pool) { _usage--; if (!_disposed) { _pool.Push(item); return; } } DestroyItem(item); } public void Dispose() { lock (_pool) { _disposed = true; foreach (var item in _pool) DestroyItem(item); _pool.Clear(); } } ~BatchStreamPoolBase() { Dispose(); } } internal sealed class BatchStreamObjectPool : BatchStreamPoolBase where T : class? { public int ArraySize { get; } public BatchStreamObjectPool(int arraySize = 128, Action>? startTimer = null) : base(false, startTimer) { ArraySize = arraySize; } protected override T[] CreateItem() { return new T[ArraySize]; } protected override void DestroyItem(T[] item) { Array.Clear(item, 0, item.Length); } } internal sealed class BatchStreamMemoryPool : BatchStreamPoolBase { public int BufferSize { get; } public BatchStreamMemoryPool(int bufferSize = 1024, Action>? startTimer = null) : base(true, startTimer) { BufferSize = bufferSize; } protected override IntPtr CreateItem() => Marshal.AllocHGlobal(BufferSize); protected override void DestroyItem(IntPtr item) => Marshal.FreeHGlobal(item); }