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