using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Threading; namespace Cysharp.Threading.Tasks { // internaly used but public, allow to user create custom operator with pooling. public static class TaskPool { internal static int MaxPoolSize; // avoid to use ConcurrentDictionary for safety of WebGL build. static Dictionary> sizes = new Dictionary>(); static TaskPool() { try { var value = Environment.GetEnvironmentVariable("UNITASK_MAX_POOLSIZE"); if (value != null) { if (int.TryParse(value, out var size)) { MaxPoolSize = size; return; } } } catch { } MaxPoolSize = int.MaxValue; } public static void SetMaxPoolSize(int maxPoolSize) { MaxPoolSize = maxPoolSize; } public static IEnumerable<(Type, int)> GetCacheSizeInfo() { lock (sizes) { foreach (var item in sizes) { yield return (item.Key, item.Value()); } } } public static void RegisterSizeGetter(Type type, Func getSize) { lock (sizes) { sizes[type] = getSize; } } } public interface ITaskPoolNode { ref T NextNode { get; } } // mutable struct, don't mark readonly. [StructLayout(LayoutKind.Auto)] public struct TaskPool where T : class, ITaskPoolNode { int gate; int size; T root; public int Size => size; [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryPop(out T result) { if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) { var v = root; if (!(v is null)) { ref var nextNode = ref v.NextNode; root = nextNode; nextNode = null; size--; result = v; Volatile.Write(ref gate, 0); return true; } Volatile.Write(ref gate, 0); } result = default; return false; } [MethodImpl(MethodImplOptions.AggressiveInlining)] public bool TryPush(T item) { if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) { if (size < TaskPool.MaxPoolSize) { item.NextNode = root; root = item; size++; Volatile.Write(ref gate, 0); return true; } else { Volatile.Write(ref gate, 0); } } return false; } } }