refactor(ui): 移除UI实例管理策略和缓存功能

- 移除UiInstancePolicy枚举类型
- 从Push、Replace和Show方法中移除instancePolicy参数
- 从IUiFactory接口中移除缓存相关方法和GetOrCreate方法
- 简化GodotUiFactory实现类,移除缓存池和实例管理逻辑
- 将Pop操作中的Cache策略重命名为Suspend
- 将Exclusive策略描述从Pause+Hide改为Pause+Suspend
- 修复CanvasItemUiPageBehavior中OnResume方法的节点有效性检查
- [release ci]
This commit is contained in:
GeWuYou 2026-02-06 22:39:49 +08:00
parent f1c3bc5a1d
commit aaf728ad1a
8 changed files with 59 additions and 544 deletions

View File

@ -7,7 +7,7 @@
public enum UiTransitionPolicy
{
/// <summary>
/// 独占显示(下层页面 Pause + Hide
/// 独占显示(下层页面 Pause + Suspend
/// </summary>
Exclusive,

View File

@ -11,75 +11,6 @@ public interface IUiFactory : IContextUtility
/// 创建或获取UI页面实例
/// </summary>
/// <param name="uiKey">UI标识键</param>
/// <param name="policy">实例管理策略</param>
/// <returns>UI页面实例</returns>
IUiPageBehavior GetOrCreate(string uiKey, UiInstancePolicy policy = UiInstancePolicy.AlwaysCreate);
/// <summary>
/// 仅创建新实例(不使用缓存)
/// </summary>
IUiPageBehavior Create(string uiKey);
/// <summary>
/// 预加载UI资源到缓存池
/// </summary>
/// <param name="uiKey">UI标识键</param>
/// <param name="count">预加载数量默认1个</param>
void Preload(string uiKey, int count = 1);
/// <summary>
/// 批量预加载
/// </summary>
void PreloadBatch(params string[] uiKeys);
/// <summary>
/// 回收实例到缓存池
/// </summary>
/// <param name="page">要回收的页面实例</param>
void Recycle(IUiPageBehavior page);
/// <summary>
/// 清理指定UI的缓存实例
/// </summary>
void ClearCache(string uiKey);
/// <summary>
/// 清理所有缓存
/// </summary>
void ClearAllCache();
/// <summary>
/// 检查是否有缓存的实例
/// </summary>
bool HasCached(string uiKey);
#region
/// <summary>
/// 获取UI的缓存配置
/// </summary>
/// <param name="uiKey">UI标识符</param>
/// <returns>缓存配置,如果未设置则返回默认配置</returns>
UiCacheConfig GetCacheConfig(string uiKey);
/// <summary>
/// 设置UI的缓存配置
/// </summary>
/// <param name="uiKey">UI标识符</param>
/// <param name="config">缓存配置</param>
void SetCacheConfig(string uiKey, UiCacheConfig config);
/// <summary>
/// 移除UI的缓存配置恢复默认配置
/// </summary>
/// <param name="uiKey">UI标识符</param>
void RemoveCacheConfig(string uiKey);
/// <summary>
/// 获取所有UI的缓存统计信息
/// </summary>
/// <returns>缓存统计字典</returns>
IDictionary<string, IUiCacheStatistics> GetCacheStatistics();
#endregion
}

View File

@ -25,9 +25,7 @@ public interface IUiRouter : ISystem
/// <param name="uiKey">UI界面的唯一标识符</param>
/// <param name="param">进入界面的参数,可为空</param>
/// <param name="policy">界面切换策略默认为Exclusive独占</param>
/// <param name="instancePolicy">实例管理策略默认为Reuse复用</param>
void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive);
/// <summary>
@ -54,13 +52,11 @@ public interface IUiRouter : ISystem
/// <param name="param">页面进入参数,可为空</param>
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
/// <param name="instancePolicy">实例管理策略</param>
public void Replace(
string uiKey,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive);
/// <summary>
/// 替换当前所有页面为已存在的页面(基于实例)
@ -146,12 +142,10 @@ public interface IUiRouter : ISystem
/// <param name="uiKey">要显示的UI页面的唯一标识符</param>
/// <param name="layer">UI显示的层级例如 Overlay、Modal 或 Toast</param>
/// <param name="param">可选参数用于传递给UI页面的初始化数据</param>
/// <param name="instancePolicy">UI实例策略默认为复用已存在的实例</param>
void Show(
string uiKey,
UiLayer layer,
IUiPageEnterParam? param = null,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
IUiPageEnterParam? param = null);
/// <summary>
/// 在指定层级显示UIOverlay / Modal / Toast等

View File

@ -1,22 +0,0 @@
namespace GFramework.Game.Abstractions.ui;
/// <summary>
/// UI页面实例管理策略控制实例的生命周期
/// </summary>
public enum UiInstancePolicy
{
/// <summary>
/// 总是创建新实例
/// </summary>
AlwaysCreate,
/// <summary>
/// 复用已存在的实例(如果有)
/// </summary>
Reuse,
/// <summary>
/// 从预加载池中获取或创建
/// </summary>
Pooled
}

View File

@ -11,7 +11,7 @@ public enum UiPopPolicy
Destroy,
/// <summary>
/// 隐藏但保留实例下次Push可复用)
///
/// </summary>
Cache
Suspend
}

View File

@ -76,10 +76,8 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <param name="uiKey">UI界面的唯一标识符</param>
/// <param name="param">进入界面的参数,可为空</param>
/// <param name="policy">界面切换策略默认为Exclusive独占</param>
/// <param name="instancePolicy">实例管理策略默认为Reuse复用</param>
public void Push(string uiKey, IUiPageEnterParam? param = null,
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive)
{
if (IsTop(uiKey))
{
@ -90,12 +88,12 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
Log.Debug(
"Push UI Page: key={0}, policy={1}, instancePolicy={2}, stackBefore={3}",
uiKey, policy, instancePolicy, _stack.Count
"Push UI Page: key={0}, policy={1}, stackBefore={2}",
uiKey, policy, _stack.Count
);
BeforeChange(@event);
DoPushPageInternal(uiKey, param, policy, instancePolicy);
DoPushPageInternal(uiKey, param, policy);
AfterChange(@event);
}
@ -176,18 +174,16 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <param name="param">页面进入参数,可为空</param>
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
/// <param name="instancePolicy">实例管理策略</param>
public void Replace(
string uiKey,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive)
{
var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param);
Log.Debug(
"Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}, instancePolicy={3}",
uiKey, popPolicy, pushPolicy, instancePolicy
"Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}",
uiKey, popPolicy, pushPolicy
);
BeforeChange(@event);
@ -196,7 +192,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
DoClearInternal(popPolicy);
// 使用工厂的增强方法获取实例
var page = _factory.GetOrCreate(uiKey, instancePolicy);
var page = _factory.Create(uiKey);
Log.Debug("Get/Create UI Page instance for Replace: {0}", page.GetType().Name);
DoPushPageInternal(page, param, pushPolicy);
@ -366,8 +362,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <summary>
/// 执行Push页面的核心逻辑基于 uiKey
/// </summary>
private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy,
UiInstancePolicy instancePolicy)
private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy)
{
// 执行进入守卫
if (!ExecuteEnterGuardsAsync(uiKey, param).GetAwaiter().GetResult())
@ -377,7 +372,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
}
// 使用工厂的增强方法获取实例
var page = _factory.GetOrCreate(uiKey, instancePolicy);
var page = _factory.Create(uiKey);
Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name);
DoPushPageInternal(page, param, policy);
@ -397,7 +392,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
if (policy == UiTransitionPolicy.Exclusive)
{
Log.Debug("Hide current page (Exclusive): {0}", current.View.GetType().Name);
Log.Debug("Suspend current page (Exclusive): {0}", current.View.GetType().Name);
current.OnHide();
}
}
@ -428,37 +423,26 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
return;
var top = _stack.Pop();
Log.Debug(
"Pop UI Page internal: {0}, policy={1}, stackAfterPop={2}",
top.GetType().Name, policy, _stack.Count
);
top.OnExit();
if (policy == UiPopPolicy.Destroy)
{
Log.Debug("Destroy UI Page: {0}", top.GetType().Name);
top.OnExit();
_uiRoot.RemoveUiPage(top);
// 不回收,直接销毁
}
else // UiPopPolicy.Cache
else // Suspend
{
Log.Debug("Cache UI Page: {0}", top.GetType().Name);
_uiRoot.RemoveUiPage(top);
_factory.Recycle(top); // 回收到池中
top.OnHide();
}
if (_stack.Count > 0)
{
var next = _stack.Peek();
Log.Debug("Resume & Show page: {0}", next.GetType().Name);
next.OnResume();
next.OnShow();
}
else
{
Log.Debug("UI stack is now empty");
}
if (_stack.Count <= 0) return;
var next = _stack.Peek();
next.OnResume();
next.OnShow();
}
/// <summary>
@ -481,8 +465,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
public void Show(
string uiKey,
UiLayer layer,
IUiPageEnterParam? param = null,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
IUiPageEnterParam? param = null)
{
if (layer == UiLayer.Page) throw new ArgumentException("Use Push() for Page layer");
@ -502,7 +485,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
}
// 获取或创建实例
var page = _factory.GetOrCreate(uiKey, instancePolicy);
var page = _factory.Create(uiKey);
layerDict[uiKey] = page;
// 添加到UiRoot传入层级Z-order
@ -580,14 +563,11 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
{
_uiRoot.RemoveUiPage(page);
layerDict.Remove(uiKey);
Log.Debug("Hide & Destroy UI from layer: {0}, layer={1}", uiKey, layer);
Log.Debug("Suspend & Destroy UI from layer: {0}, layer={1}", uiKey, layer);
}
else
{
_uiRoot.RemoveUiPage(page);
_factory.Recycle(page);
layerDict.Remove(uiKey);
Log.Debug("Hide & Cache UI from layer: {0}, layer={1}", uiKey, layer);
Log.Debug("Suspend & Suspend UI from layer: {0}, layer={1}", uiKey, layer);
}
}

View File

@ -68,6 +68,11 @@ public class CanvasItemUiPageBehavior<T>(T owner, string key) : IUiPageBehavior
/// </summary>
public void OnResume()
{
if (owner.IsInvalidNode())
{
return;
}
_page?.OnResume();
// 恢复节点的处理、物理处理和输入处理

View File

@ -2,435 +2,62 @@ using GFramework.Core.Abstractions.logging;
using GFramework.Core.extensions;
using GFramework.Core.logging;
using GFramework.Core.utility;
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using GFramework.Godot.extensions;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// Godot UI工厂类用于创建UI页面实例
/// 继承自AbstractContextUtility并实现IUiFactory接口
/// Godot UI工厂类用于创建UI页面实例
/// 继承自AbstractContextUtility并实现IUiFactory接口
/// </summary>
public class GodotUiFactory : AbstractContextUtility, IUiFactory
{
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("GodotUiFactory");
/// <summary>
/// 日志记录器,用于记录调试信息。
/// </summary>
private static readonly ILogger Log =
LoggerFactoryResolver.Provider.CreateLogger("GodotUiFactory");
/// <summary>
/// LFU访问计数instance -> 访问次数
/// UI注册表用于管理UI场景资源。
/// </summary>
private readonly Dictionary<IUiPageBehavior, int> _accessCount = new();
/// <summary>
/// LRU访问时间队列uiKey -> 按访问时间排序的实例列表
/// </summary>
private readonly Dictionary<string, List<(IUiPageBehavior instance, DateTime accessTime)>> _accessTimeQueue = new();
/// <summary>
/// 追踪所有创建的实例(用于清理)
/// </summary>
private readonly Dictionary<string, HashSet<IUiPageBehavior>> _allInstances = new();
/// <summary>
/// 缓存配置uiKey -> 配置
/// </summary>
private readonly Dictionary<string, UiCacheConfig> _cacheConfigs = new();
/// <summary>
/// 缓存池uiKey -> 实例队列
/// </summary>
private readonly Dictionary<string, Queue<IUiPageBehavior>> _cachedInstances = new();
/// <summary>
/// 缓存统计uiKey -> 统计信息
/// </summary>
private readonly Dictionary<string, CacheStatisticsInfo> _cacheStatistics = new();
private IGodotUiRegistry _registry = null!;
/// <summary>
/// 创建或获取UI页面实例
/// </summary>
public IUiPageBehavior GetOrCreate(string uiKey, UiInstancePolicy policy = UiInstancePolicy.AlwaysCreate)
{
return policy switch
{
UiInstancePolicy.Reuse => GetCachedOrCreate(uiKey),
UiInstancePolicy.Pooled => GetFromPoolOrCreate(uiKey),
_ => Create(uiKey)
};
}
/// <summary>
/// 仅创建新实例
/// 根据指定的UI键创建UI页面实例。
/// </summary>
/// <param name="uiKey">UI页面的唯一标识符。</param>
/// <returns>返回创建的UI页面行为实例。</returns>
/// <exception cref="InvalidCastException">
/// 当UI场景未实现IUiPageBehaviorProvider接口时抛出异常。
/// </exception>
public IUiPageBehavior Create(string uiKey)
{
// 从注册表中获取指定UI键对应的场景
var scene = _registry.Get(uiKey);
// 实例化场景节点
var node = scene.Instantiate();
// 检查节点是否实现了IUiPageBehaviorProvider接口
if (node is not IUiPageBehaviorProvider provider)
throw new InvalidCastException($"UI scene {uiKey} must implement IUiPageBehaviorProvider");
throw new InvalidCastException(
$"UI scene {uiKey} must implement IUiPageBehaviorProvider");
// 获取页面行为实例
var page = provider.GetPage();
// 追踪实例
if (!_allInstances.ContainsKey(uiKey))
_allInstances[uiKey] = new HashSet<IUiPageBehavior>();
_allInstances[uiKey].Add(page);
Log.Debug("Created new UI instance: {0}", uiKey);
// 记录调试日志
Log.Debug("Created UI instance: {0}", uiKey);
return page;
}
/// <summary>
/// 预加载UI资源
/// 初始化方法,在对象初始化时调用。
/// 获取并设置UI注册表实例。
/// </summary>
public void Preload(string uiKey, int count = 1)
{
Log.Debug("Preloading UI: {0}, count={1}", uiKey, count);
if (!_cachedInstances.ContainsKey(uiKey))
_cachedInstances[uiKey] = new Queue<IUiPageBehavior>();
var queue = _cachedInstances[uiKey];
for (var i = 0; i < count; i++)
{
var instance = Create(uiKey);
// 预加载的实例初始状态为隐藏
instance.OnHide();
queue.Enqueue(instance);
}
Log.Debug("Preloaded {0} instances of {1}", count, uiKey);
}
/// <summary>
/// 批量预加载
/// </summary>
public void PreloadBatch(params string[] uiKeys)
{
foreach (var uiKey in uiKeys) Preload(uiKey);
}
/// <summary>
/// 回收实例到缓存池
/// </summary>
public void Recycle(IUiPageBehavior page)
{
var uiKey = page.Key;
if (!_cachedInstances.ContainsKey(uiKey))
_cachedInstances[uiKey] = new Queue<IUiPageBehavior>();
// 确保实例处于隐藏状态
page.OnHide();
// 更新统计信息
UpdateStatisticsOnRecycle(uiKey);
// 更新访问追踪
UpdateAccessTracking(uiKey, page);
_cachedInstances[uiKey].Enqueue(page);
Log.Debug("Recycled UI instance to pool: {0}, poolSize={1}", uiKey, _cachedInstances[uiKey].Count);
// 检查是否需要淘汰
CheckAndEvict(uiKey);
}
/// <summary>
/// 获取UI的缓存配置
/// </summary>
public UiCacheConfig GetCacheConfig(string uiKey)
{
return _cacheConfigs.TryGetValue(uiKey, out var config) ? config : UiCacheConfig.Default;
}
/// <summary>
/// 设置UI的缓存配置
/// </summary>
public void SetCacheConfig(string uiKey, UiCacheConfig config)
{
_cacheConfigs[uiKey] = config;
Log.Debug("Set cache config for UI: {0}, MaxSize={1}, Policy={2}", uiKey, config.MaxCacheSize,
config.EvictionPolicy);
// 检查是否需要淘汰
CheckAndEvict(uiKey);
}
/// <summary>
/// 移除UI的缓存配置
/// </summary>
public void RemoveCacheConfig(string uiKey)
{
if (_cacheConfigs.Remove(uiKey)) Log.Debug("Removed cache config for UI: {0}", uiKey);
}
/// <summary>
/// 获取所有UI的缓存统计信息
/// </summary>
public IDictionary<string, IUiCacheStatistics> GetCacheStatistics()
{
var result = new Dictionary<string, IUiCacheStatistics>();
foreach (var kvp in _cacheStatistics) result[kvp.Key] = kvp.Value;
return result;
}
/// <summary>
/// 清理指定UI的缓存
/// </summary>
public void ClearCache(string uiKey)
{
if (!_cachedInstances.TryGetValue(uiKey, out var queue))
return;
var count = queue.Count;
while (queue.Count > 0)
{
var instance = queue.Dequeue();
DestroyInstance(instance);
}
_cachedInstances.Remove(uiKey);
Log.Debug("Cleared cache for UI: {0}, destroyed {1} instances", uiKey, count);
}
/// <summary>
/// 清理所有缓存
/// </summary>
public void ClearAllCache()
{
foreach (var uiKey in _cachedInstances.Keys) ClearCache(uiKey);
Log.Debug("Cleared all UI caches");
}
/// <summary>
/// 检查是否有缓存的实例
/// </summary>
public bool HasCached(string uiKey)
{
return _cachedInstances.TryGetValue(uiKey, out var queue) && queue.Count > 0;
}
protected override void OnInit()
{
_registry = this.GetUtility<IGodotUiRegistry>()!;
}
/// <summary>
/// 缓存统计信息实现类
/// </summary>
private sealed class CacheStatisticsInfo : IUiCacheStatistics
{
public int CacheSize { get; set; }
public int HitCount { get; set; }
public int MissCount { get; set; }
public double HitRate => HitCount + MissCount > 0 ? (double)HitCount / (HitCount + MissCount) : 0;
public DateTime? LastAccessTime { get; set; }
}
#region Private Methods
/// <summary>
/// 获取缓存实例或创建新实例Reuse策略
/// </summary>
private IUiPageBehavior GetCachedOrCreate(string uiKey)
{
// 优先从缓存池获取
if (_cachedInstances.TryGetValue(uiKey, out var queue) && queue.Count > 0)
{
var cached = queue.Dequeue();
// 更新统计:缓存命中
UpdateStatisticsOnHit(uiKey);
// 更新访问追踪
UpdateAccessTracking(uiKey, cached);
Log.Debug("Reused cached UI instance: {0}, remainingInPool={1}", uiKey, queue.Count);
return cached;
}
// 没有缓存则创建新实例
UpdateStatisticsOnMiss(uiKey);
Log.Debug("No cached instance, creating new: {0}", uiKey);
return Create(uiKey);
}
/// <summary>
/// 从池中获取或创建Pooled策略
/// 如果池为空,自动创建并填充
/// </summary>
private IUiPageBehavior GetFromPoolOrCreate(string uiKey)
{
// 如果池为空,先预加载一个
if (HasCached(uiKey)) return GetCachedOrCreate(uiKey);
Log.Debug("Pool empty, preloading instance: {0}", uiKey);
Preload(uiKey);
return GetCachedOrCreate(uiKey);
}
/// <summary>
/// 销毁实例
/// </summary>
private void DestroyInstance(IUiPageBehavior page)
{
var uiKey = page.Key;
// 从追踪列表移除
if (_allInstances.TryGetValue(uiKey, out var set)) set.Remove(page);
// 从访问追踪移除
_accessCount.Remove(page);
if (_accessTimeQueue.TryGetValue(uiKey, out var queue)) queue.RemoveAll(x => x.instance == page);
// 销毁Godot节点
if (page.View is Node node) node.QueueFreeX();
}
/// <summary>
/// 更新统计信息:回收
/// </summary>
private void UpdateStatisticsOnRecycle(string uiKey)
{
if (!_cacheStatistics.ContainsKey(uiKey))
_cacheStatistics[uiKey] = new CacheStatisticsInfo();
var stats = _cacheStatistics[uiKey];
stats.CacheSize = _cachedInstances[uiKey].Count + 1;
stats.LastAccessTime = DateTime.Now;
}
/// <summary>
/// 更新统计信息:命中
/// </summary>
private void UpdateStatisticsOnHit(string uiKey)
{
if (!_cacheStatistics.ContainsKey(uiKey))
_cacheStatistics[uiKey] = new CacheStatisticsInfo();
var stats = _cacheStatistics[uiKey];
stats.HitCount++;
stats.CacheSize = _cachedInstances[uiKey].Count;
stats.LastAccessTime = DateTime.Now;
}
/// <summary>
/// 更新统计信息:未命中
/// </summary>
private void UpdateStatisticsOnMiss(string uiKey)
{
if (!_cacheStatistics.ContainsKey(uiKey))
_cacheStatistics[uiKey] = new CacheStatisticsInfo();
var stats = _cacheStatistics[uiKey];
stats.MissCount++;
stats.CacheSize = _cachedInstances.TryGetValue(uiKey, out var queue) ? queue.Count : 0;
}
/// <summary>
/// 更新访问追踪
/// </summary>
private void UpdateAccessTracking(string uiKey, IUiPageBehavior instance)
{
var now = DateTime.Now;
// LRU: 更新访问时间队列
if (!_accessTimeQueue.ContainsKey(uiKey))
_accessTimeQueue[uiKey] = new List<(IUiPageBehavior, DateTime)>();
var timeQueue = _accessTimeQueue[uiKey];
timeQueue.RemoveAll(x => x.instance == instance);
timeQueue.Add((instance, now));
// LFU: 更新访问计数
_accessCount.TryGetValue(instance, out var count);
_accessCount[instance] = count + 1;
}
/// <summary>
/// 检查并执行淘汰
/// </summary>
private void CheckAndEvict(string uiKey)
{
var config = GetCacheConfig(uiKey);
var currentSize = _cachedInstances.TryGetValue(uiKey, out var queue) ? queue.Count : 0;
if (currentSize <= config.MaxCacheSize) return;
var toEvict = currentSize - config.MaxCacheSize;
for (var i = 0; i < toEvict; i++)
if (config.EvictionPolicy == CacheEvictionPolicy.Lru)
EvictLru(uiKey);
else
EvictLfu(uiKey);
Log.Debug("Evicted {0} instances for UI: {1}", toEvict, uiKey);
}
/// <summary>
/// LRU淘汰策略
/// </summary>
private void EvictLru(string uiKey)
{
if (!_accessTimeQueue.TryGetValue(uiKey, out var timeQueue) || timeQueue.Count == 0)
return;
var oldest = timeQueue.OrderBy(x => x.accessTime).First();
// 从队列中移除
if (_cachedInstances.TryGetValue(uiKey, out var queue))
{
var tempQueue = new Queue<IUiPageBehavior>();
var removed = false;
while (queue.Count > 0)
{
var item = queue.Dequeue();
if (!removed && item == oldest.instance)
{
DestroyInstance(item);
removed = true;
}
else
{
tempQueue.Enqueue(item);
}
}
// 重新填充队列
while (tempQueue.Count > 0)
queue.Enqueue(tempQueue.Dequeue());
}
}
/// <summary>
/// LFU淘汰策略
/// </summary>
private void EvictLfu(string uiKey)
{
if (!_cachedInstances.TryGetValue(uiKey, out var queue) || queue.Count == 0)
return;
// 找到访问次数最少的实例
IUiPageBehavior? toRemove = null;
var minCount = int.MaxValue;
foreach (var instance in queue)
if (_accessCount.TryGetValue(instance, out var count) && count < minCount)
{
minCount = count;
toRemove = instance;
}
if (toRemove != null) DestroyInstance(toRemove);
}
#endregion
}