mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-25 13:33:28 +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;
|
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>
|
/// <summary>
|
||||||
/// UI工厂接口,用于创建UI页面实例
|
/// UI工厂接口,用于创建UI页面实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -52,4 +85,34 @@ public interface IUiFactory : IContextUtility
|
|||||||
/// 检查是否有缓存的实例
|
/// 检查是否有缓存的实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
bool HasCached(string uiKey);
|
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>
|
/// </summary>
|
||||||
bool Contains(string uiKey);
|
bool Contains(string uiKey);
|
||||||
|
|
||||||
#region 层级管理
|
#region 路由守卫
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在指定层级显示UI(非栈管理)
|
/// 注册路由守卫
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uiKey">UI标识符</param>
|
/// <param name="guard">守卫实例</param>
|
||||||
/// <param name="layer">UI层级</param>
|
void AddGuard(IUiRouteGuard guard);
|
||||||
/// <param name="param">进入参数</param>
|
|
||||||
/// <param name="instancePolicy">实例策略</param>
|
|
||||||
void Show(
|
|
||||||
string uiKey,
|
|
||||||
UiLayer layer,
|
|
||||||
IUiPageEnterParam? param = null,
|
|
||||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在指定层级显示UI(基于实例)
|
/// 移除路由守卫
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="page">UI页面实例</param>
|
/// <param name="guard">守卫实例</param>
|
||||||
/// <param name="layer">UI层级</param>
|
void RemoveGuard(IUiRouteGuard guard);
|
||||||
void Show(IUiPageBehavior page, UiLayer layer);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 隐藏指定层级的UI
|
/// 注册路由守卫(泛型方法)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uiKey">UI标识符</param>
|
/// <typeparam name="T">守卫类型,必须实现 IUiRouteGuard 且有无参构造函数</typeparam>
|
||||||
/// <param name="layer">UI层级</param>
|
void AddGuard<T>() where T : IUiRouteGuard, new();
|
||||||
/// <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);
|
|
||||||
|
|
||||||
#endregion
|
#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!;
|
private IUiRoot _uiRoot = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 路由守卫列表
|
||||||
|
/// </summary>
|
||||||
|
private readonly List<IUiRouteGuard> _guards = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 注册UI切换处理器
|
/// 注册UI切换处理器
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -92,13 +97,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
);
|
);
|
||||||
|
|
||||||
BeforeChange(@event);
|
BeforeChange(@event);
|
||||||
|
DoPushPageInternal(uiKey, param, policy, instancePolicy, animationPolicy);
|
||||||
// 使用工厂的增强方法获取实例
|
|
||||||
var page = _factory.GetOrCreate(uiKey, instancePolicy);
|
|
||||||
Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name);
|
|
||||||
|
|
||||||
DoPushPageInternal(page, param, policy);
|
|
||||||
|
|
||||||
AfterChange(@event);
|
AfterChange(@event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,6 +148,15 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
return;
|
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
|
var nextUiKey = _stack.Count > 1
|
||||||
? _stack.ElementAt(1).Key // 使用 Key 而不是 View.GetType().Name
|
? _stack.ElementAt(1).Key // 使用 Key 而不是 View.GetType().Name
|
||||||
: throw new InvalidOperationException("Stack is empty");
|
: throw new InvalidOperationException("Stack is empty");
|
||||||
@ -362,8 +370,27 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
private void DoPushPageInternal(IUiPageBehavior page, IUiPageEnterParam? param, UiTransitionPolicy policy)
|
private void DoPushPageInternal(IUiPageBehavior page, IUiPageEnterParam? param, UiTransitionPolicy policy)
|
||||||
{
|
{
|
||||||
@ -582,4 +609,124 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#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.extensions;
|
||||||
using GFramework.Core.logging;
|
using GFramework.Core.logging;
|
||||||
using GFramework.Core.utility;
|
using GFramework.Core.utility;
|
||||||
@ -14,6 +15,18 @@ namespace GFramework.Godot.ui;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
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 static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("GodotUiFactory");
|
||||||
|
|
||||||
private IGodotUiRegistry _registry = null!;
|
private IGodotUiRegistry _registry = null!;
|
||||||
@ -27,6 +40,26 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
|||||||
/// 追踪所有创建的实例(用于清理)
|
/// 追踪所有创建的实例(用于清理)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Dictionary<string, HashSet<IUiPageBehavior>> _allInstances = new();
|
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>
|
/// <summary>
|
||||||
/// 创建或获取UI页面实例
|
/// 创建或获取UI页面实例
|
||||||
@ -110,10 +143,63 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
|||||||
// 确保实例处于隐藏状态
|
// 确保实例处于隐藏状态
|
||||||
page.OnHide();
|
page.OnHide();
|
||||||
|
|
||||||
|
// 更新统计信息
|
||||||
|
UpdateStatisticsOnRecycle(uiKey);
|
||||||
|
|
||||||
|
// 更新访问追踪
|
||||||
|
UpdateAccessTracking(uiKey, page);
|
||||||
|
|
||||||
_cachedInstances[uiKey].Enqueue(page);
|
_cachedInstances[uiKey].Enqueue(page);
|
||||||
Log.Debug("Recycled UI instance to pool: {0}, poolSize={1}", uiKey, _cachedInstances[uiKey].Count);
|
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>
|
/// <summary>
|
||||||
/// 清理指定UI的缓存
|
/// 清理指定UI的缓存
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -170,11 +256,19 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
|||||||
if (_cachedInstances.TryGetValue(uiKey, out var queue) && queue.Count > 0)
|
if (_cachedInstances.TryGetValue(uiKey, out var queue) && queue.Count > 0)
|
||||||
{
|
{
|
||||||
var cached = queue.Dequeue();
|
var cached = queue.Dequeue();
|
||||||
|
|
||||||
|
// 更新统计:缓存命中
|
||||||
|
UpdateStatisticsOnHit(uiKey);
|
||||||
|
|
||||||
|
// 更新访问追踪
|
||||||
|
UpdateAccessTracking(uiKey, cached);
|
||||||
|
|
||||||
Log.Debug("Reused cached UI instance: {0}, remainingInPool={1}", uiKey, queue.Count);
|
Log.Debug("Reused cached UI instance: {0}, remainingInPool={1}", uiKey, queue.Count);
|
||||||
return cached;
|
return cached;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有缓存则创建新实例
|
// 没有缓存则创建新实例
|
||||||
|
UpdateStatisticsOnMiss(uiKey);
|
||||||
Log.Debug("No cached instance, creating new: {0}", uiKey);
|
Log.Debug("No cached instance, creating new: {0}", uiKey);
|
||||||
return Create(uiKey);
|
return Create(uiKey);
|
||||||
}
|
}
|
||||||
@ -205,6 +299,13 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
|||||||
{
|
{
|
||||||
set.Remove(page);
|
set.Remove(page);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 从访问追踪移除
|
||||||
|
_accessCount.Remove(page);
|
||||||
|
if (_accessTimeQueue.TryGetValue(uiKey, out var queue))
|
||||||
|
{
|
||||||
|
queue.RemoveAll(x => x.instance == page);
|
||||||
|
}
|
||||||
|
|
||||||
// 销毁Godot节点
|
// 销毁Godot节点
|
||||||
if (page.View is Node node)
|
if (page.View is Node node)
|
||||||
@ -212,6 +313,153 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
|||||||
node.QueueFreeX();
|
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
|
#endregion
|
||||||
}
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user