// 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})";
}
}
}