// MIT License - Copyright (c) 2026 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Utils { using System; /// /// Configuration options for auto-purging behavior. /// /// The type of objects managed by the pool. /// /// /// Use these options to control memory usage by limiting pool size and purging idle items. /// All timing values use seconds as the unit. /// /// /// For intelligent purging that avoids GC churn, enable /// and configure the settings via or set properties directly. /// Intelligent purging tracks usage patterns and only purges items that are unlikely to be needed. /// /// /// options = new() /// { /// MaxPoolSize = 100, /// IdleTimeoutSeconds = 60f, /// Triggers = PurgeTrigger.OnRent | PurgeTrigger.OnReturn, /// OnPurge = (item, reason) => Debug.Log($"Purged: {reason}") /// }; /// /// // Enable intelligent purging /// PoolOptions smartOptions = new() /// { /// UseIntelligentPurging = true, /// IdleTimeoutSeconds = 300f, // 5 minutes /// BufferMultiplier = 1.5f, /// RollingWindowSeconds = 300f, /// HysteresisSeconds = 60f /// }; /// ]]> /// /// public sealed class PoolOptions { /// /// Default value for (0 = unbounded). /// public const int DefaultMaxPoolSize = 0; /// /// Default value for . /// public const int DefaultMinRetainCount = 0; /// /// Default value for . /// public const int DefaultWarmRetainCount = 2; /// /// Default value for (0 = disabled). /// public const float DefaultIdleTimeoutSeconds = 0f; /// /// Default value for . /// public const float DefaultPurgeIntervalSeconds = 60f; /// /// Default value for . /// public const float DefaultBufferMultiplier = 2.0f; /// /// Default value for . /// public const float DefaultRollingWindowSeconds = 300f; /// /// Default value for . /// public const float DefaultHysteresisSeconds = 120f; /// /// Default value for . /// public const float DefaultSpikeThresholdMultiplier = 2.5f; /// /// Default value for . /// public const int DefaultMaxPurgesPerOperation = 10; /// /// Maximum number of items to retain in the pool. /// Items exceeding this limit will be purged. /// A value of 0 or null means unbounded (no size limit). /// public int? MaxPoolSize { get; set; } /// /// Minimum number of items to always retain in the pool during purge operations. /// This is the absolute floor - purge will never reduce the pool below this count. /// Default is 0 (no minimum). /// public int MinRetainCount { get; set; } = DefaultMinRetainCount; /// /// Warm retain count for active pools. /// Active pools (accessed within ) keep this many items warm /// to avoid cold-start allocations. Idle pools purge to . /// Effective floor = max(MinRetainCount, isActive ? WarmRetainCount : 0). /// Default is 2. /// public int? WarmRetainCount { get; set; } /// /// Time in seconds after which an idle item becomes eligible for purging. /// A value of 0 or null disables idle timeout purging. /// Items are tracked from the time they are returned to the pool. /// public float? IdleTimeoutSeconds { get; set; } /// /// Interval in seconds between periodic purge checks when is enabled. /// Only used when includes . /// Default is 60 seconds. /// public float? PurgeIntervalSeconds { get; set; } /// /// Specifies when automatic purge operations should be triggered. /// Default is for time-based cleanup that avoids /// per-operation overhead in hot paths. /// public PurgeTrigger Triggers { get; set; } = PurgeTrigger.Periodic; /// /// Optional callback invoked when an item is purged from the pool. /// The callback receives the purged item and the reason for purging. /// Use this for cleanup, logging, or resource disposal. /// /// /// Exceptions thrown by this callback are swallowed to prevent pool corruption. /// public Action OnPurge { get; set; } /// /// Optional function to provide the current time for idle tracking. /// If null, uses a Stopwatch-based provider (safe during static initialization). /// Useful for testing or custom time sources. /// public Func TimeProvider { get; set; } /// /// When true, enables intelligent purging that tracks usage patterns to avoid GC churn. /// If null, consults for the effective setting. /// Default is null (uses global settings, which default to disabled). /// /// /// /// Intelligent purging provides the following protections against GC churn: /// /// Tracks rolling high-water mark of concurrent rentals /// Only purges items that are BOTH idle for AND would leave pool above "comfortable size" /// Applies hysteresis after usage spikes to prevent purge-allocate cycles /// Never purges if it would bring pool below typical usage level /// /// /// public bool? UseIntelligentPurging { get; set; } /// /// Buffer multiplier for calculating "comfortable" pool size. /// The comfortable size is max(MinRetainCount, rollingHighWaterMark * BufferMultiplier). /// Default is 1.5 (50% buffer above peak usage). /// public float? BufferMultiplier { get; set; } /// /// Duration in seconds for the rolling window used to track high-water mark. /// Only the peak concurrent rentals within this window are considered. /// Default is 300 seconds (5 minutes). /// public float? RollingWindowSeconds { get; set; } /// /// Hysteresis duration in seconds after a usage spike. /// Purging is suppressed for this duration after a spike to prevent purge-allocate cycles. /// Default is 60 seconds. /// public float? HysteresisSeconds { get; set; } /// /// Multiplier to determine what constitutes a "spike" in usage. /// A spike is detected when concurrent rentals exceed the rolling average by this factor. /// Default is 2.0 (spike is 2x the average). /// public float? SpikeThresholdMultiplier { get; set; } /// /// Maximum number of items to purge per operation. /// Limits GC pressure by spreading large purge operations across multiple calls. /// A value of 0 means unlimited (purge all eligible items in one operation). /// If null, uses global default from . /// Default is null (uses global setting, which defaults to 10). /// /// /// /// When set to a positive value, purge operations will process at most this many items /// before returning, setting a "pending purges" flag to continue on subsequent operations. /// This prevents GC spikes from bulk deallocation. /// /// /// Emergency purges (e.g., ) bypass this limit /// to ensure memory is freed immediately when the system is under memory pressure. /// /// public int? MaxPurgesPerOperation { get; set; } } }