mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
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:
parent
f1c3bc5a1d
commit
aaf728ad1a
@ -7,7 +7,7 @@
|
||||
public enum UiTransitionPolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// 独占显示(下层页面 Pause + Hide)
|
||||
/// 独占显示(下层页面 Pause + Suspend)
|
||||
/// </summary>
|
||||
Exclusive,
|
||||
|
||||
|
||||
@ -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
|
||||
}
|
||||
@ -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>
|
||||
/// 在指定层级显示UI(Overlay / Modal / Toast等)
|
||||
|
||||
@ -1,22 +0,0 @@
|
||||
namespace GFramework.Game.Abstractions.ui;
|
||||
|
||||
/// <summary>
|
||||
/// UI页面实例管理策略(控制实例的生命周期)
|
||||
/// </summary>
|
||||
public enum UiInstancePolicy
|
||||
{
|
||||
/// <summary>
|
||||
/// 总是创建新实例
|
||||
/// </summary>
|
||||
AlwaysCreate,
|
||||
|
||||
/// <summary>
|
||||
/// 复用已存在的实例(如果有)
|
||||
/// </summary>
|
||||
Reuse,
|
||||
|
||||
/// <summary>
|
||||
/// 从预加载池中获取或创建
|
||||
/// </summary>
|
||||
Pooled
|
||||
}
|
||||
@ -11,7 +11,7 @@ public enum UiPopPolicy
|
||||
Destroy,
|
||||
|
||||
/// <summary>
|
||||
/// 隐藏但保留实例(下次Push可复用)
|
||||
/// 可恢复
|
||||
/// </summary>
|
||||
Cache
|
||||
Suspend
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -68,6 +68,11 @@ public class CanvasItemUiPageBehavior<T>(T owner, string key) : IUiPageBehavior
|
||||
/// </summary>
|
||||
public void OnResume()
|
||||
{
|
||||
if (owner.IsInvalidNode())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_page?.OnResume();
|
||||
|
||||
// 恢复节点的处理、物理处理和输入处理
|
||||
|
||||
@ -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
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user