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