// MIT License - Copyright (c) 2026 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Utils { using System; using UnityEngine; using UnityEngine.Serialization; /// /// Serializable per-type configuration for pool purging behavior. /// /// /// /// This class stores type-specific pool configuration that can be serialized in /// Unity project settings. It supports generic types via assembly-qualified type names. /// /// /// Configuration priority (highest to lowest): /// /// Programmatic API (PoolPurgeSettings.Configure) /// Per-type configuration from settings /// PoolPurgePolicyAttribute on the type /// Global defaults from settings /// Hardcoded defaults /// /// /// [Serializable] public sealed class PoolTypeConfiguration { /// /// Type name in any supported format. /// /// /// /// Supported formats include: /// /// System.Collections.Generic.List`1 - Open generic CLR syntax /// System.Collections.Generic.List`1[[System.Int32]] - Closed generic CLR syntax /// List<int> - Simplified closed generic /// List<> - Simplified open generic (matches any List) /// Dictionary<string, int> - Multiple type arguments /// Dictionary<,> - Open with multiple type arguments /// List<List<int>> - Nested generics /// List<List<>> - Nested with inner open /// /// /// [FormerlySerializedAs("typeName")] [SerializeField] [Tooltip( "Type name. Supports: List, List<>, Dictionary, Dictionary<,>, List>, System.Collections.Generic.List`1" )] internal string _typeName = string.Empty; /// /// Whether intelligent pool purging is enabled for this type. /// [FormerlySerializedAs("enabled")] [SerializeField] [Tooltip("Whether intelligent pool purging is enabled for this type.")] internal bool _enabled = true; /// /// Idle timeout in seconds before items become eligible for purging. /// A value of 0 or less disables idle-based purging. /// [FormerlySerializedAs("idleTimeoutSeconds")] [SerializeField] [Tooltip( "Idle timeout in seconds before items become eligible for purging. 0 disables idle-based purging." )] [Min(0f)] internal float _idleTimeoutSeconds = PoolPurgeSettings.DefaultIdleTimeoutSeconds; /// /// Minimum number of items to always retain in the pool during purge operations. /// This is the absolute floor - pools never purge below this. /// [FormerlySerializedAs("minRetainCount")] [SerializeField] [Tooltip( "Minimum number of items to always retain during purge operations. Absolute floor." )] [Min(0)] internal int _minRetainCount = PoolPurgeSettings.DefaultMinRetainCount; /// /// Warm retain count for active pools. /// Active pools (accessed within IdleTimeoutSeconds) keep this many items warm /// to avoid cold-start allocations. /// [SerializeField] [Tooltip( "Warm retain count for active pools. Active pools keep this many items warm to avoid cold-start allocations." )] [Min(0)] internal int _warmRetainCount = PoolPurgeSettings.DefaultWarmRetainCount; /// /// Maximum pool size. Items exceeding this limit will be purged. /// A value of 0 or less means unbounded. /// [FormerlySerializedAs("maxPoolSize")] [SerializeField] [Tooltip("Maximum pool size. 0 means unbounded.")] [Min(0)] internal int _maxPoolSize; /// /// Buffer multiplier for comfortable pool size calculation. /// Comfortable size = max(MinRetainCount, rollingHighWaterMark * BufferMultiplier). /// [FormerlySerializedAs("bufferMultiplier")] [SerializeField] [Tooltip( "Buffer multiplier for comfortable pool size calculation. Higher values retain more items." )] [Min(1f)] internal float _bufferMultiplier = PoolPurgeSettings.DefaultBufferMultiplier; /// /// Rolling window duration in seconds for high water mark tracking. /// [FormerlySerializedAs("rollingWindowSeconds")] [SerializeField] [Tooltip("Rolling window duration in seconds for high water mark tracking.")] [Min(1f)] internal float _rollingWindowSeconds = PoolPurgeSettings.DefaultRollingWindowSeconds; /// /// Hysteresis duration in seconds. Purging is suppressed for this duration after a usage spike. /// [FormerlySerializedAs("hysteresisSeconds")] [SerializeField] [Tooltip("Hysteresis duration in seconds. Purging is suppressed after a usage spike.")] [Min(0f)] internal float _hysteresisSeconds = PoolPurgeSettings.DefaultHysteresisSeconds; /// /// Spike threshold multiplier. A spike is detected when concurrent rentals exceed /// the rolling average by this factor. /// [FormerlySerializedAs("spikeThresholdMultiplier")] [SerializeField] [Tooltip( "Spike threshold multiplier. A spike is detected when concurrent rentals exceed the rolling average by this factor." )] [Min(1f)] internal float _spikeThresholdMultiplier = PoolPurgeSettings.DefaultSpikeThresholdMultiplier; /// /// Gets or sets the full type name including assembly. /// public string TypeName { get => _typeName ?? string.Empty; set => _typeName = value ?? string.Empty; } /// /// Gets or sets whether intelligent purging is enabled for this type. /// public bool Enabled { get => _enabled; set => _enabled = value; } /// /// Gets or sets the idle timeout in seconds. /// public float IdleTimeoutSeconds { get => _idleTimeoutSeconds; set => _idleTimeoutSeconds = value < 0f ? 0f : value; } /// /// Gets or sets the minimum retain count. /// public int MinRetainCount { get => _minRetainCount; set => _minRetainCount = value < 0 ? 0 : value; } /// /// Gets or sets the warm retain count for active pools. /// public int WarmRetainCount { get => _warmRetainCount; set => _warmRetainCount = value < 0 ? 0 : value; } /// /// Gets or sets the maximum pool size. /// public int MaxPoolSize { get => _maxPoolSize; set => _maxPoolSize = value < 0 ? 0 : value; } /// /// Gets or sets the buffer multiplier. /// public float BufferMultiplier { get => _bufferMultiplier; set => _bufferMultiplier = value < 1f ? 1f : value; } /// /// Gets or sets the rolling window duration in seconds. /// public float RollingWindowSeconds { get => _rollingWindowSeconds; set => _rollingWindowSeconds = value < 1f ? 1f : value; } /// /// Gets or sets the hysteresis duration in seconds. /// public float HysteresisSeconds { get => _hysteresisSeconds; set => _hysteresisSeconds = value < 0f ? 0f : value; } /// /// Gets or sets the spike threshold multiplier. /// public float SpikeThresholdMultiplier { get => _spikeThresholdMultiplier; set => _spikeThresholdMultiplier = value < 1f ? 1f : value; } /// /// Creates a new pool type configuration with default values. /// public PoolTypeConfiguration() { } /// /// Creates a new pool type configuration for the specified type. /// /// The full type name including assembly. public PoolTypeConfiguration(string typeName) { TypeName = typeName; } /// /// Creates a new pool type configuration from the specified type. /// /// The type to configure. public PoolTypeConfiguration(Type type) { TypeName = type?.AssemblyQualifiedName ?? string.Empty; } private Type _resolvedType; private bool _resolvedTypeCached; private string _cachedTypeName; /// /// Gets the resolved for this configuration. /// The result is cached for performance. /// /// /// Uses for type resolution, which supports /// simplified generic syntax like List<int> and open generics like List<>. /// public Type ResolvedType { get { string currentTypeName = TypeName; if ( !_resolvedTypeCached || !string.Equals(_cachedTypeName, currentTypeName, StringComparison.Ordinal) ) { _resolvedType = PoolTypeResolver.ResolveType(currentTypeName); _cachedTypeName = currentTypeName; _resolvedTypeCached = true; } return _resolvedType; } } /// /// Gets whether the configured type is an open generic type definition. /// /// /// An open generic type definition is a type like List<> that can match /// any closed generic type like List<int>, List<string>, etc. /// public bool IsOpenGeneric { get { Type type = ResolvedType; return type != null && type.IsGenericTypeDefinition; } } /// /// Attempts to resolve the configured type name to a Type instance. /// /// The resolved Type, or null if the type could not be resolved. /// /// This method uses which supports: /// /// Assembly-qualified names /// Open generic CLR syntax (System.Collections.Generic.List`1) /// Closed generic CLR syntax (System.Collections.Generic.List`1[[System.Int32]]) /// Simplified open generic syntax (List<>) /// Simplified closed generic syntax (List<int>) /// Nested generics (List<List<int>>) /// /// public Type ResolveType() { return ResolvedType; } /// /// Checks if this configuration matches the specified concrete type. /// /// The concrete type to check. /// /// true if this configuration matches the type; otherwise, false. /// /// /// /// Matching rules: /// /// Exact type match always succeeds /// Open generic definitions match any closed generic /// (e.g., List<> matches List<int>) /// Partially open generics match corresponding types /// (e.g., List<List<>> matches List<List<int>>) /// /// /// public bool Matches(Type concreteType) { if (concreteType == null) { return false; } Type patternType = ResolvedType; if (patternType == null) { return false; } return PoolTypeResolver.TypeMatchesPattern(concreteType, patternType); } /// /// Gets the match priority for this configuration when matching against a concrete type. /// Lower values indicate higher priority (more specific match). /// /// The concrete type being matched. /// /// A priority value where: /// /// 0 = exact match /// 1 = partially open generic (inner args open) /// 2 = fully open generic definition /// = no match /// /// public int GetMatchPriority(Type concreteType) { if (concreteType == null) { return int.MaxValue; } Type patternType = ResolvedType; if (patternType == null) { return int.MaxValue; } return PoolTypeResolver.GetMatchPriority(concreteType, patternType); } /// /// Invalidates the cached resolved type, forcing re-resolution on next access. /// public void InvalidateCache() { _resolvedTypeCached = false; _resolvedType = null; _cachedTypeName = null; } /// /// Converts this configuration to a PoolPurgeTypeOptions instance. /// /// A PoolPurgeTypeOptions instance with the configured values. public PoolPurgeTypeOptions ToPoolPurgeTypeOptions() { return new PoolPurgeTypeOptions { Enabled = _enabled, IdleTimeoutSeconds = _idleTimeoutSeconds, MinRetainCount = _minRetainCount, WarmRetainCount = _warmRetainCount, BufferMultiplier = _bufferMultiplier, RollingWindowSeconds = _rollingWindowSeconds, HysteresisSeconds = _hysteresisSeconds, SpikeThresholdMultiplier = _spikeThresholdMultiplier, MaxPoolSize = _maxPoolSize, }; } /// public override string ToString() { return $"PoolTypeConfiguration(Type={_typeName}, Enabled={_enabled}, IdleTimeout={_idleTimeoutSeconds}s, " + $"MinRetain={_minRetainCount}, WarmRetain={_warmRetainCount}, MaxSize={_maxPoolSize}, Buffer={_bufferMultiplier})"; } } }