mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
feat(ui): 添加UI缓存统计和路由守卫功能
- 新增IUiCacheStatistics接口用于UI缓存统计信息 - 为IUiFactory添加缓存策略管理和统计信息获取功能 - 将IUiRouter中的层级管理改为路由守卫功能 - 实现路由守卫的注册、移除和执行逻辑 - 添加缓存配置管理支持 - [skip ci]
This commit is contained in:
parent
e972d926a7
commit
ad061bba46
@ -1,7 +1,40 @@
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using GFramework.Core.Abstractions.utility;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI缓存统计信息接口
|
||||
/// </summary>
|
||||
public interface IUiCacheStatistics
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存总数
|
||||
/// </summary>
|
||||
int CacheSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 缓存命中次数
|
||||
/// </summary>
|
||||
int HitCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 缓存未命中次数
|
||||
/// </summary>
|
||||
int MissCount { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 命中率
|
||||
/// </summary>
|
||||
double HitRate { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 最近访问时间
|
||||
/// </summary>
|
||||
DateTime? LastAccessTime { get; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// UI工厂接口,用于创建UI页面实例
|
||||
/// </summary>
|
||||
@ -52,4 +85,34 @@ public interface IUiFactory : IContextUtility
|
||||
/// 检查是否有缓存的实例
|
||||
/// </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
|
||||
}
|
||||
36
GFramework.Game.Abstractions/ui/IUiRouteGuard.cs
Normal file
36
GFramework.Game.Abstractions/ui/IUiRouteGuard.cs
Normal file
@ -0,0 +1,36 @@
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI路由守卫接口
|
||||
/// 用于拦截和处理UI路由切换,实现业务逻辑解耦
|
||||
/// </summary>
|
||||
public interface IUiRouteGuard
|
||||
{
|
||||
/// <summary>
|
||||
/// 守卫优先级,数值越小越先执行
|
||||
/// </summary>
|
||||
int Priority { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否可中断后续守卫
|
||||
/// 如果返回 true,当该守卫返回 false 时,将停止执行后续守卫
|
||||
/// </summary>
|
||||
bool CanInterrupt { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 进入UI前的检查
|
||||
/// </summary>
|
||||
/// <param name="uiKey">目标UI标识符</param>
|
||||
/// <param name="param">进入参数</param>
|
||||
/// <returns>true表示允许进入,false表示拦截</returns>
|
||||
Task<bool> CanEnterAsync(string uiKey, IUiPageEnterParam? param);
|
||||
|
||||
/// <summary>
|
||||
/// 离开UI前的检查
|
||||
/// </summary>
|
||||
/// <param name="uiKey">当前UI标识符</param>
|
||||
/// <returns>true表示允许离开,false表示拦截</returns>
|
||||
Task<bool> CanLeaveAsync(string uiKey);
|
||||
}
|
||||
@ -121,57 +121,25 @@ public interface IUiRouter : ISystem
|
||||
/// </summary>
|
||||
bool Contains(string uiKey);
|
||||
|
||||
#region 层级管理
|
||||
#region 路由守卫
|
||||
|
||||
/// <summary>
|
||||
/// 在指定层级显示UI(非栈管理)
|
||||
/// 注册路由守卫
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI标识符</param>
|
||||
/// <param name="layer">UI层级</param>
|
||||
/// <param name="param">进入参数</param>
|
||||
/// <param name="instancePolicy">实例策略</param>
|
||||
void Show(
|
||||
string uiKey,
|
||||
UiLayer layer,
|
||||
IUiPageEnterParam? param = null,
|
||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
|
||||
/// <param name="guard">守卫实例</param>
|
||||
void AddGuard(IUiRouteGuard guard);
|
||||
|
||||
/// <summary>
|
||||
/// 在指定层级显示UI(基于实例)
|
||||
/// 移除路由守卫
|
||||
/// </summary>
|
||||
/// <param name="page">UI页面实例</param>
|
||||
/// <param name="layer">UI层级</param>
|
||||
void Show(IUiPageBehavior page, UiLayer layer);
|
||||
/// <param name="guard">守卫实例</param>
|
||||
void RemoveGuard(IUiRouteGuard guard);
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏指定层级的UI
|
||||
/// 注册路由守卫(泛型方法)
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI标识符</param>
|
||||
/// <param name="layer">UI层级</param>
|
||||
/// <param name="destroy">是否销毁实例</param>
|
||||
void Hide(string uiKey, UiLayer layer, bool destroy = false);
|
||||
|
||||
/// <summary>
|
||||
/// 清空指定层级的所有UI
|
||||
/// </summary>
|
||||
/// <param name="layer">UI层级</param>
|
||||
/// <param name="destroy">是否销毁实例</param>
|
||||
void ClearLayer(UiLayer layer, bool destroy = false);
|
||||
|
||||
/// <summary>
|
||||
/// 获取指定层级的UI实例
|
||||
/// </summary>
|
||||
/// <param name="uiKey">UI标识符</param>
|
||||
/// <param name="layer">UI层级</param>
|
||||
/// <returns>UI实例,不存在则返回null</returns>
|
||||
IUiPageBehavior? GetFromLayer(string uiKey, UiLayer layer);
|
||||
|
||||
/// <summary>
|
||||
/// 判断指定层级是否有UI显示
|
||||
/// </summary>
|
||||
/// <param name="layer">UI层级</param>
|
||||
/// <returns>是否有UI显示</returns>
|
||||
bool HasVisibleInLayer(UiLayer layer);
|
||||
/// <typeparam name="T">守卫类型,必须实现 IUiRouteGuard 且有无参构造函数</typeparam>
|
||||
void AddGuard<T>() where T : IUiRouteGuard, new();
|
||||
|
||||
#endregion
|
||||
}
|
||||
77
GFramework.Game.Abstractions/ui/UiCacheConfig.cs
Normal file
77
GFramework.Game.Abstractions/ui/UiCacheConfig.cs
Normal file
@ -0,0 +1,77 @@
|
||||
using System;
|
||||
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI缓存配置
|
||||
/// 用于配置UI实例的缓存行为
|
||||
/// </summary>
|
||||
public class UiCacheConfig
|
||||
{
|
||||
/// <summary>
|
||||
/// 最大缓存数量
|
||||
/// </summary>
|
||||
public int MaxCacheSize { get; set; } = 10;
|
||||
|
||||
/// <summary>
|
||||
/// 缓存淘汰策略
|
||||
/// </summary>
|
||||
public CacheEvictionPolicy EvictionPolicy { get; set; } = CacheEvictionPolicy.LRU;
|
||||
|
||||
/// <summary>
|
||||
/// 访问后过期时间(可选,null 表示不启用)
|
||||
/// </summary>
|
||||
public TimeSpan? ExpireAfterAccess { get; set; } = null;
|
||||
|
||||
/// <summary>
|
||||
/// 创建默认配置(LRU 策略,最大 10 个实例)
|
||||
/// </summary>
|
||||
public static UiCacheConfig Default => new UiCacheConfig
|
||||
{
|
||||
MaxCacheSize = 10,
|
||||
EvictionPolicy = CacheEvictionPolicy.LRU,
|
||||
ExpireAfterAccess = null
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 创建 LRU 策略配置
|
||||
/// </summary>
|
||||
/// <param name="maxSize">最大缓存数量</param>
|
||||
/// <param name="expireAfter">访问后过期时间</param>
|
||||
public static UiCacheConfig Lru(int maxSize = 10, TimeSpan? expireAfter = null)
|
||||
=> new UiCacheConfig
|
||||
{
|
||||
MaxCacheSize = maxSize,
|
||||
EvictionPolicy = CacheEvictionPolicy.LRU,
|
||||
ExpireAfterAccess = expireAfter
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// 创建 LFU 策略配置
|
||||
/// </summary>
|
||||
/// <param name="maxSize">最大缓存数量</param>
|
||||
/// <param name="expireAfter">访问后过期时间</param>
|
||||
public static UiCacheConfig Lfu(int maxSize = 10, TimeSpan? expireAfter = null)
|
||||
=> new UiCacheConfig
|
||||
{
|
||||
MaxCacheSize = maxSize,
|
||||
EvictionPolicy = CacheEvictionPolicy.LFU,
|
||||
ExpireAfterAccess = expireAfter
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 缓存淘汰策略枚举
|
||||
/// </summary>
|
||||
public enum CacheEvictionPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// 最近最少使用
|
||||
/// </summary>
|
||||
LRU,
|
||||
|
||||
/// <summary>
|
||||
/// 最少使用频率
|
||||
/// </summary>
|
||||
LFU
|
||||
}
|
||||
@ -37,6 +37,11 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
|
||||
private IUiRoot _uiRoot = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 路由守卫列表
|
||||
/// </summary>
|
||||
private readonly List<IUiRouteGuard> _guards = new();
|
||||
|
||||
/// <summary>
|
||||
/// 注册UI切换处理器
|
||||
/// </summary>
|
||||
@ -92,13 +97,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
);
|
||||
|
||||
BeforeChange(@event);
|
||||
|
||||
// 使用工厂的增强方法获取实例
|
||||
var page = _factory.GetOrCreate(uiKey, instancePolicy);
|
||||
Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name);
|
||||
|
||||
DoPushPageInternal(page, param, policy);
|
||||
|
||||
DoPushPageInternal(uiKey, param, policy, instancePolicy, animationPolicy);
|
||||
AfterChange(@event);
|
||||
}
|
||||
|
||||
@ -149,6 +148,15 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
return;
|
||||
}
|
||||
|
||||
var topUiKey = _stack.Peek().Key;
|
||||
|
||||
// 执行离开守卫
|
||||
if (!ExecuteLeaveGuardsAsync(topUiKey).GetAwaiter().GetResult())
|
||||
{
|
||||
Log.Warn("Pop blocked by guard: {0}", topUiKey);
|
||||
return;
|
||||
}
|
||||
|
||||
var nextUiKey = _stack.Count > 1
|
||||
? _stack.ElementAt(1).Key // 使用 Key 而不是 View.GetType().Name
|
||||
: throw new InvalidOperationException("Stack is empty");
|
||||
@ -362,8 +370,27 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行Push页面的核心逻辑(统一处理)
|
||||
/// 这个方法同时服务于工厂创建和已存在页面两种情况
|
||||
/// 执行Push页面的核心逻辑(基于 uiKey)
|
||||
/// </summary>
|
||||
private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy,
|
||||
UiInstancePolicy instancePolicy, UiAnimationPolicy? animationPolicy)
|
||||
{
|
||||
// 执行进入守卫
|
||||
if (!ExecuteEnterGuardsAsync(uiKey, param).GetAwaiter().GetResult())
|
||||
{
|
||||
Log.Warn("Push blocked by guard: {0}", uiKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// 使用工厂的增强方法获取实例
|
||||
var page = _factory.GetOrCreate(uiKey, instancePolicy);
|
||||
Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name);
|
||||
|
||||
DoPushPageInternal(page, param, policy);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行Push页面的核心逻辑(基于 page)
|
||||
/// </summary>
|
||||
private void DoPushPageInternal(IUiPageBehavior page, IUiPageEnterParam? param, UiTransitionPolicy policy)
|
||||
{
|
||||
@ -582,4 +609,124 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region 路由守卫
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// 注册路由守卫
|
||||
/// </summary>
|
||||
public void AddGuard(IUiRouteGuard guard)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guard);
|
||||
|
||||
if (_guards.Contains(guard))
|
||||
{
|
||||
Log.Debug("Guard already registered: {0}", guard.GetType().Name);
|
||||
return;
|
||||
}
|
||||
|
||||
_guards.Add(guard);
|
||||
// 按优先级排序
|
||||
_guards.Sort((a, b) => a.Priority.CompareTo(b.Priority));
|
||||
Log.Debug("Guard registered: {0}, Priority={1}", guard.GetType().Name, guard.Priority);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 移除路由守卫
|
||||
/// </summary>
|
||||
public void RemoveGuard(IUiRouteGuard guard)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(guard);
|
||||
|
||||
if (_guards.Remove(guard))
|
||||
{
|
||||
Log.Debug("Guard removed: {0}", guard.GetType().Name);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 注册路由守卫(泛型方法)
|
||||
/// </summary>
|
||||
public void AddGuard<T>() where T : IUiRouteGuard, new()
|
||||
{
|
||||
var guard = new T();
|
||||
AddGuard(guard);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行进入守卫
|
||||
/// </summary>
|
||||
private async Task<bool> ExecuteEnterGuardsAsync(string uiKey, IUiPageEnterParam? param)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, uiKey);
|
||||
var canEnter = await guard.CanEnterAsync(uiKey, param);
|
||||
|
||||
if (!canEnter)
|
||||
{
|
||||
Log.Debug("Enter guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Enter guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 执行离开守卫
|
||||
/// </summary>
|
||||
private async Task<bool> ExecuteLeaveGuardsAsync(string uiKey)
|
||||
{
|
||||
foreach (var guard in _guards)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, uiKey);
|
||||
var canLeave = await guard.CanLeaveAsync(uiKey);
|
||||
|
||||
if (!canLeave)
|
||||
{
|
||||
Log.Debug("Leave guard blocked: {0}", guard.GetType().Name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
Log.Debug("Leave guard {0} passed, can interrupt = true", guard.GetType().Name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Leave guard {0} failed: {1}", guard.GetType().Name, ex.Message);
|
||||
if (guard.CanInterrupt)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
@ -1,4 +1,5 @@
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using System;
|
||||
using GFramework.Core.Abstractions.logging;
|
||||
using GFramework.Core.extensions;
|
||||
using GFramework.Core.logging;
|
||||
using GFramework.Core.utility;
|
||||
@ -14,6 +15,18 @@ namespace GFramework.Godot.ui;
|
||||
/// </summary>
|
||||
public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 缓存统计信息实现类
|
||||
/// </summary>
|
||||
private 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; }
|
||||
}
|
||||
|
||||
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("GodotUiFactory");
|
||||
|
||||
private IGodotUiRegistry _registry = null!;
|
||||
@ -27,6 +40,26 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
||||
/// 追踪所有创建的实例(用于清理)
|
||||
/// </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, CacheStatisticsInfo> _cacheStatistics = new();
|
||||
|
||||
/// <summary>
|
||||
/// LRU访问时间队列:uiKey -> 按访问时间排序的实例列表
|
||||
/// </summary>
|
||||
private readonly Dictionary<string, List<(IUiPageBehavior instance, DateTime accessTime)>> _accessTimeQueue = new();
|
||||
|
||||
/// <summary>
|
||||
/// LFU访问计数:instance -> 访问次数
|
||||
/// </summary>
|
||||
private readonly Dictionary<IUiPageBehavior, int> _accessCount = new();
|
||||
|
||||
/// <summary>
|
||||
/// 创建或获取UI页面实例
|
||||
@ -110,10 +143,63 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
||||
// 确保实例处于隐藏状态
|
||||
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>
|
||||
@ -170,11 +256,19 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
||||
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);
|
||||
}
|
||||
@ -205,6 +299,13 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
||||
{
|
||||
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)
|
||||
@ -212,6 +313,153 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
||||
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)
|
||||
{
|
||||
var toEvict = currentSize - config.MaxCacheSize;
|
||||
|
||||
for (int 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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user