// MIT License - Copyright (c) 2026 wallstop // Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE namespace WallstopStudios.UnityHelpers.Core.DataStructure { using System; /// /// Fluent builder for constructing instances. /// This is a value type to avoid allocations during builder construction. /// /// /// cache = CacheBuilder.NewBuilder() /// .MaximumSize(1000) /// .ExpireAfterWrite(TimeSpan.FromMinutes(5)) /// .EvictionPolicy(EvictionPolicy.Lru) /// .RecordStatistics() /// .Build(); /// ]]> /// /// The type of keys in the cache. /// The type of values in the cache. public struct CacheBuilder { private int _maximumSize; private int _initialCapacity; private long _maximumWeight; private Func _weigher; private float _expireAfterWriteSeconds; private float _expireAfterAccessSeconds; private Func _expireAfter; private bool _useJitter; private float _jitterMaxSeconds; private EvictionPolicy _policy; private float _protectedRatio; private bool _allowGrowth; private float _growthFactor; private int _maxGrowthSize; private float _thrashThresholdEvictionsPerSecond; private Action _onEviction; private Action _onGet; private Action _onSet; private bool _recordStatistics; private Func _timeProvider; private Func _loader; private bool _initialized; /// /// Creates a new cache builder with default settings. /// /// A new instance. public static CacheBuilder NewBuilder() { CacheBuilder builder = default; builder._maximumSize = CacheOptions.DefaultMaximumSize; builder._expireAfterWriteSeconds = CacheOptions< TKey, TValue >.DefaultExpireAfterWriteSeconds; builder._expireAfterAccessSeconds = CacheOptions< TKey, TValue >.DefaultExpireAfterAccessSeconds; builder._policy = CacheOptions.DefaultEvictionPolicy; builder._protectedRatio = CacheOptions.DefaultProtectedRatio; builder._growthFactor = CacheOptions.DefaultGrowthFactor; builder._thrashThresholdEvictionsPerSecond = CacheOptions< TKey, TValue >.DefaultThrashThreshold; builder._initialized = true; return builder; } /// /// Sets the maximum number of entries the cache can hold. /// /// The maximum entry count. Must be positive. /// This builder for chaining. public CacheBuilder MaximumSize(int size) { EnsureInitialized(); if (size <= 0) { size = 1; } _maximumSize = size; return this; } /// /// Sets the initial capacity of the cache's internal data structures. /// The cache will grow dynamically as needed up to . /// /// /// This is useful when you know the approximate number of entries the cache will hold /// initially. Setting this appropriately can reduce memory allocations during growth. /// If not specified, the cache defaults to using as initial capacity. /// If an invalid value (zero or negative) is passed, falls back to /// . /// Values are clamped to prevent excessive initial allocations. /// /// The initial capacity. Must be positive. /// This builder for chaining. public CacheBuilder InitialCapacity(int capacity) { EnsureInitialized(); if (capacity <= 0) { capacity = CacheOptions.DefaultInitialCapacity; } _initialCapacity = capacity; return this; } /// /// Sets the maximum total weight of all cache entries. /// Requires a weigher to be specified. /// /// The maximum total weight. Must be positive. /// This builder for chaining. public CacheBuilder MaximumWeight(long weight) { EnsureInitialized(); if (weight <= 0) { weight = 1; } _maximumWeight = weight; return this; } /// /// Sets the function used to compute entry weights. /// When specified, is used instead of . /// /// Function that computes the weight of an entry. /// This builder for chaining. public CacheBuilder Weigher(Func weigher) { EnsureInitialized(); _weigher = weigher; return this; } /// /// Sets the time after which entries expire following creation or update. /// /// Expiration time in seconds. Non-positive values disable expiration. /// This builder for chaining. public CacheBuilder ExpireAfterWrite(float seconds) { EnsureInitialized(); _expireAfterWriteSeconds = seconds; return this; } /// /// Sets the time after which entries expire following creation or update. /// /// Expiration duration. /// This builder for chaining. public CacheBuilder ExpireAfterWrite(TimeSpan duration) { EnsureInitialized(); _expireAfterWriteSeconds = (float)duration.TotalSeconds; return this; } /// /// Sets the time after which entries expire following last access (sliding window). /// /// Expiration time in seconds. Non-positive values disable expiration. /// This builder for chaining. public CacheBuilder ExpireAfterAccess(float seconds) { EnsureInitialized(); _expireAfterAccessSeconds = seconds; return this; } /// /// Sets the time after which entries expire following last access (sliding window). /// /// Expiration duration. /// This builder for chaining. public CacheBuilder ExpireAfterAccess(TimeSpan duration) { EnsureInitialized(); _expireAfterAccessSeconds = (float)duration.TotalSeconds; return this; } /// /// Sets a function to compute custom per-entry expiration times. /// /// Function that returns expiration time in seconds for an entry. /// This builder for chaining. public CacheBuilder ExpireAfter(Func expireAfter) { EnsureInitialized(); _expireAfter = expireAfter; return this; } /// /// Enables jitter on expiration times to prevent thundering herd. /// /// Maximum jitter in seconds. If null, defaults to 10% of TTL. /// This builder for chaining. public CacheBuilder WithJitter(float maxJitterSeconds = 0f) { EnsureInitialized(); _useJitter = true; if (maxJitterSeconds > 0f) { _jitterMaxSeconds = maxJitterSeconds; } return this; } /// /// Sets the eviction policy used when the cache reaches capacity. /// /// The eviction algorithm to use. /// This builder for chaining. public CacheBuilder EvictionPolicy(EvictionPolicy policy) { EnsureInitialized(); _policy = policy; return this; } /// /// Sets the protected segment ratio for SLRU eviction policy. /// /// Value between 0 and 1. Default is 0.8 (80% protected). /// This builder for chaining. public CacheBuilder ProtectedRatio(float ratio) { EnsureInitialized(); if (ratio < 0f) { ratio = 0f; } else if (ratio > 1f) { ratio = 1f; } _protectedRatio = ratio; return this; } /// /// Enables dynamic cache growth when thrashing is detected. /// Pass factor <= 1 and maxSize = 0 to disable growth. /// /// Growth factor. Default is 1.5x. Values <= 1 disable growth. /// Maximum size after growth. 0 means unbounded (if enabled). /// This builder for chaining. public CacheBuilder AllowGrowth(float factor = 1.5f, int maxSize = 0) { EnsureInitialized(); // If factor <= 1 and maxSize is 0, disable growth if (factor <= 1f && maxSize == 0) { _allowGrowth = false; return this; } _allowGrowth = true; if (factor > 1f) { _growthFactor = factor; } if (maxSize > 0) { _maxGrowthSize = maxSize; } return this; } /// /// Sets the evictions per second threshold for thrash detection. /// /// Threshold value. /// This builder for chaining. public CacheBuilder ThrashThreshold(float evictionsPerSecond) { EnsureInitialized(); if (evictionsPerSecond > 0f) { _thrashThresholdEvictionsPerSecond = evictionsPerSecond; } return this; } /// /// Sets the callback invoked when entries are evicted. /// /// Callback receiving the key, value, and eviction reason. /// This builder for chaining. public CacheBuilder OnEviction(Action listener) { EnsureInitialized(); _onEviction = listener; return this; } /// /// Sets the callback invoked when entries are retrieved. /// /// Callback receiving the key and value. /// This builder for chaining. public CacheBuilder OnGet(Action listener) { EnsureInitialized(); _onGet = listener; return this; } /// /// Sets the callback invoked when entries are added or updated. /// /// Callback receiving the key and value. /// This builder for chaining. public CacheBuilder OnSet(Action listener) { EnsureInitialized(); _onSet = listener; return this; } /// /// Enables statistics recording for the cache. /// /// This builder for chaining. public CacheBuilder RecordStatistics() { EnsureInitialized(); _recordStatistics = true; return this; } /// /// Sets a custom time provider for the cache. /// /// Function returning the current time in seconds. /// This builder for chaining. public CacheBuilder TimeProvider(Func provider) { EnsureInitialized(); _timeProvider = provider; return this; } /// /// Builds the cache with the configured options. /// /// A new instance. public Cache Build() { EnsureInitialized(); return new Cache(CreateOptions()); } /// /// Builds a loading cache with the configured options. /// /// Factory function to compute values for missing keys. /// A new instance with automatic loading. public Cache Build(Func loader) { EnsureInitialized(); _loader = loader; return new Cache(CreateOptions()); } private void EnsureInitialized() { if (_initialized) { return; } _maximumSize = CacheOptions.DefaultMaximumSize; _expireAfterWriteSeconds = CacheOptions.DefaultExpireAfterWriteSeconds; _expireAfterAccessSeconds = CacheOptions.DefaultExpireAfterAccessSeconds; _policy = CacheOptions.DefaultEvictionPolicy; _protectedRatio = CacheOptions.DefaultProtectedRatio; _growthFactor = CacheOptions.DefaultGrowthFactor; _thrashThresholdEvictionsPerSecond = CacheOptions.DefaultThrashThreshold; _initialized = true; } private CacheOptions CreateOptions() { return new CacheOptions { MaximumSize = _maximumSize, InitialCapacity = _initialCapacity, MaximumWeight = _maximumWeight, Weigher = _weigher, ExpireAfterWriteSeconds = _expireAfterWriteSeconds, ExpireAfterAccessSeconds = _expireAfterAccessSeconds, ExpireAfter = _expireAfter, UseJitter = _useJitter, JitterMaxSeconds = _jitterMaxSeconds, Policy = _policy, ProtectedRatio = _protectedRatio, AllowGrowth = _allowGrowth, GrowthFactor = _growthFactor, MaxGrowthSize = _maxGrowthSize, ThrashThresholdEvictionsPerSecond = _thrashThresholdEvictionsPerSecond, OnEviction = _onEviction, OnGet = _onGet, OnSet = _onSet, RecordStatistics = _recordStatistics, TimeProvider = _timeProvider, Loader = _loader, }; } } }