// MIT License - Copyright (c) 2025 wallstop
// Full license text: https://github.com/wallstop/unity-helpers/blob/main/LICENSE
namespace WallstopStudios.UnityHelpers.Editor.Core.Helper
{
#if UNITY_EDITOR
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using UnityEngine;
using WallstopStudios.UnityHelpers.Core.DataStructure;
using WallstopStudios.UnityHelpers.Core.Helper;
///
/// Internal wrapper for tracking LRU order per-dictionary.
/// Uses a LinkedList to maintain access order and a Dictionary for O(1) node lookup.
///
/// The type of dictionary key.
internal sealed class LRUOrderTracker
{
private readonly LinkedList _accessOrder = new();
private readonly Dictionary> _nodeMap = new();
///
/// Marks a key as recently accessed by moving it to the end of the access order.
/// If the key doesn't exist in tracking, adds it.
///
/// The key to mark as accessed.
public void MarkAccessed(TKey key)
{
if (_nodeMap.TryGetValue(key, out LinkedListNode node))
{
_accessOrder.Remove(node);
_accessOrder.AddLast(node);
}
else
{
LinkedListNode newNode = _accessOrder.AddLast(key);
_nodeMap[key] = newNode;
}
}
///
/// Removes a key from the LRU tracking.
///
/// The key to remove.
public void Remove(TKey key)
{
if (_nodeMap.TryGetValue(key, out LinkedListNode node))
{
_accessOrder.Remove(node);
_nodeMap.Remove(key);
}
}
///
/// Clears all keys from the LRU tracker.
/// This is useful for synchronizing the tracker when the dictionary is cleared externally.
///
public void Clear()
{
_accessOrder.Clear();
_nodeMap.Clear();
}
///
/// Gets the least recently used key (first in access order).
///
/// The LRU key if found.
/// True if there is at least one key being tracked; otherwise, false.
public bool TryGetLeastRecentlyUsed(out TKey key)
{
if (_accessOrder.First != null)
{
key = _accessOrder.First.Value;
return true;
}
key = default;
return false;
}
}
///
/// Provides centralized caching utilities for editor code to avoid repeated allocations.
///
///
/// This helper consolidates common caching patterns used across property drawers, inspectors,
/// and editor windows. Using a single cache improves memory efficiency and reduces duplication.
/// All caches use the unified implementation with LRU eviction.
///
public static class EditorCacheHelper
{
///
/// Default maximum size for bounded UI state caches (foldouts, scroll positions).
///
public const int DefaultUIStateCacheSize = 5000;
///
/// Default maximum size for bounded reflection caches (accessors, field info).
///
public const int DefaultReflectionCacheSize = 2000;
///
/// Default maximum size for bounded editor instance caches.
///
public const int DefaultEditorCacheSize = 500;
private const int MaxIntCacheSize = 10000;
private const int MaxPaginationCacheSize = 1000;
private const int MaxGUIStyleCacheSize = 500;
// Lazy initialization to avoid triggering Cache/PRNG static initialization during
// EditorCacheHelper class loading, which can cause deadlocks during Unity's
// "Open Project: Open Scene" phase.
private static Cache _intToStringCache;
private static Cache<(int, int), string> _paginationLabelCache;
///
/// LRU cache for integer-to-string conversions.
/// Used by GetCachedIntString() and pagination labels across all editor UI.
///
private static Cache IntToStringCache =>
_intToStringCache ??= CacheBuilder
.NewBuilder()
.MaximumSize(MaxIntCacheSize)
.Build();
///
/// LRU cache for pagination labels in format "Page X / Y".
///
private static Cache<(int, int), string> PaginationLabelCache =>
_paginationLabelCache ??= CacheBuilder<(int, int), string>
.NewBuilder()
.MaximumSize(MaxPaginationCacheSize)
.Build();
private static readonly Dictionary SolidTextureCache = new(
new ColorComparer()
);
private static readonly Dictionary EnumDisplayNameCache = new();
private static readonly Dictionary GUIStyleCache = new();
///
/// Tracks LRU order for bounded caches. Uses ConditionalWeakTable so that
/// when a dictionary is garbage collected, its LRU tracker is also collected.
///
private static readonly ConditionalWeakTable