mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-25 21:34:28 +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
|
public enum UiTransitionPolicy
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 独占显示(下层页面 Pause + Hide)
|
/// 独占显示(下层页面 Pause + Suspend)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Exclusive,
|
Exclusive,
|
||||||
|
|
||||||
|
|||||||
@ -11,75 +11,6 @@ public interface IUiFactory : IContextUtility
|
|||||||
/// 创建或获取UI页面实例
|
/// 创建或获取UI页面实例
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uiKey">UI标识键</param>
|
/// <param name="uiKey">UI标识键</param>
|
||||||
/// <param name="policy">实例管理策略</param>
|
|
||||||
/// <returns>UI页面实例</returns>
|
/// <returns>UI页面实例</returns>
|
||||||
IUiPageBehavior GetOrCreate(string uiKey, UiInstancePolicy policy = UiInstancePolicy.AlwaysCreate);
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 仅创建新实例(不使用缓存)
|
|
||||||
/// </summary>
|
|
||||||
IUiPageBehavior Create(string uiKey);
|
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="uiKey">UI界面的唯一标识符</param>
|
||||||
/// <param name="param">进入界面的参数,可为空</param>
|
/// <param name="param">进入界面的参数,可为空</param>
|
||||||
/// <param name="policy">界面切换策略,默认为Exclusive(独占)</param>
|
/// <param name="policy">界面切换策略,默认为Exclusive(独占)</param>
|
||||||
/// <param name="instancePolicy">实例管理策略,默认为Reuse(复用)</param>
|
void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive);
|
||||||
void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
|
|
||||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -54,13 +52,11 @@ public interface IUiRouter : ISystem
|
|||||||
/// <param name="param">页面进入参数,可为空</param>
|
/// <param name="param">页面进入参数,可为空</param>
|
||||||
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
|
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
|
||||||
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
|
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
|
||||||
/// <param name="instancePolicy">实例管理策略</param>
|
|
||||||
public void Replace(
|
public void Replace(
|
||||||
string uiKey,
|
string uiKey,
|
||||||
IUiPageEnterParam? param = null,
|
IUiPageEnterParam? param = null,
|
||||||
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
|
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
|
||||||
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
|
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive);
|
||||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 替换当前所有页面为已存在的页面(基于实例)
|
/// 替换当前所有页面为已存在的页面(基于实例)
|
||||||
@ -146,12 +142,10 @@ public interface IUiRouter : ISystem
|
|||||||
/// <param name="uiKey">要显示的UI页面的唯一标识符</param>
|
/// <param name="uiKey">要显示的UI页面的唯一标识符</param>
|
||||||
/// <param name="layer">UI显示的层级,例如 Overlay、Modal 或 Toast</param>
|
/// <param name="layer">UI显示的层级,例如 Overlay、Modal 或 Toast</param>
|
||||||
/// <param name="param">可选参数,用于传递给UI页面的初始化数据</param>
|
/// <param name="param">可选参数,用于传递给UI页面的初始化数据</param>
|
||||||
/// <param name="instancePolicy">UI实例策略,默认为复用已存在的实例</param>
|
|
||||||
void Show(
|
void Show(
|
||||||
string uiKey,
|
string uiKey,
|
||||||
UiLayer layer,
|
UiLayer layer,
|
||||||
IUiPageEnterParam? param = null,
|
IUiPageEnterParam? param = null);
|
||||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 在指定层级显示UI(Overlay / Modal / Toast等)
|
/// 在指定层级显示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,
|
Destroy,
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 隐藏但保留实例(下次Push可复用)
|
/// 可恢复
|
||||||
/// </summary>
|
/// </summary>
|
||||||
Cache
|
Suspend
|
||||||
}
|
}
|
||||||
@ -76,10 +76,8 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
/// <param name="uiKey">UI界面的唯一标识符</param>
|
/// <param name="uiKey">UI界面的唯一标识符</param>
|
||||||
/// <param name="param">进入界面的参数,可为空</param>
|
/// <param name="param">进入界面的参数,可为空</param>
|
||||||
/// <param name="policy">界面切换策略,默认为Exclusive(独占)</param>
|
/// <param name="policy">界面切换策略,默认为Exclusive(独占)</param>
|
||||||
/// <param name="instancePolicy">实例管理策略,默认为Reuse(复用)</param>
|
|
||||||
public void Push(string uiKey, IUiPageEnterParam? param = null,
|
public void Push(string uiKey, IUiPageEnterParam? param = null,
|
||||||
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
|
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive)
|
||||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
|
|
||||||
{
|
{
|
||||||
if (IsTop(uiKey))
|
if (IsTop(uiKey))
|
||||||
{
|
{
|
||||||
@ -90,12 +88,12 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
|
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
|
||||||
|
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
"Push UI Page: key={0}, policy={1}, instancePolicy={2}, stackBefore={3}",
|
"Push UI Page: key={0}, policy={1}, stackBefore={2}",
|
||||||
uiKey, policy, instancePolicy, _stack.Count
|
uiKey, policy, _stack.Count
|
||||||
);
|
);
|
||||||
|
|
||||||
BeforeChange(@event);
|
BeforeChange(@event);
|
||||||
DoPushPageInternal(uiKey, param, policy, instancePolicy);
|
DoPushPageInternal(uiKey, param, policy);
|
||||||
AfterChange(@event);
|
AfterChange(@event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,18 +174,16 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
/// <param name="param">页面进入参数,可为空</param>
|
/// <param name="param">页面进入参数,可为空</param>
|
||||||
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
|
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
|
||||||
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
|
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
|
||||||
/// <param name="instancePolicy">实例管理策略</param>
|
|
||||||
public void Replace(
|
public void Replace(
|
||||||
string uiKey,
|
string uiKey,
|
||||||
IUiPageEnterParam? param = null,
|
IUiPageEnterParam? param = null,
|
||||||
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
|
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
|
||||||
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
|
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive)
|
||||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
|
|
||||||
{
|
{
|
||||||
var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param);
|
var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param);
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
"Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}, instancePolicy={3}",
|
"Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}",
|
||||||
uiKey, popPolicy, pushPolicy, instancePolicy
|
uiKey, popPolicy, pushPolicy
|
||||||
);
|
);
|
||||||
|
|
||||||
BeforeChange(@event);
|
BeforeChange(@event);
|
||||||
@ -196,7 +192,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
DoClearInternal(popPolicy);
|
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);
|
Log.Debug("Get/Create UI Page instance for Replace: {0}", page.GetType().Name);
|
||||||
|
|
||||||
DoPushPageInternal(page, param, pushPolicy);
|
DoPushPageInternal(page, param, pushPolicy);
|
||||||
@ -366,8 +362,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 执行Push页面的核心逻辑(基于 uiKey)
|
/// 执行Push页面的核心逻辑(基于 uiKey)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy,
|
private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy)
|
||||||
UiInstancePolicy instancePolicy)
|
|
||||||
{
|
{
|
||||||
// 执行进入守卫
|
// 执行进入守卫
|
||||||
if (!ExecuteEnterGuardsAsync(uiKey, param).GetAwaiter().GetResult())
|
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);
|
Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name);
|
||||||
|
|
||||||
DoPushPageInternal(page, param, policy);
|
DoPushPageInternal(page, param, policy);
|
||||||
@ -397,7 +392,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
|
|
||||||
if (policy == UiTransitionPolicy.Exclusive)
|
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();
|
current.OnHide();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -428,38 +423,27 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
var top = _stack.Pop();
|
var top = _stack.Pop();
|
||||||
|
|
||||||
Log.Debug(
|
Log.Debug(
|
||||||
"Pop UI Page internal: {0}, policy={1}, stackAfterPop={2}",
|
"Pop UI Page internal: {0}, policy={1}, stackAfterPop={2}",
|
||||||
top.GetType().Name, policy, _stack.Count
|
top.GetType().Name, policy, _stack.Count
|
||||||
);
|
);
|
||||||
|
|
||||||
top.OnExit();
|
|
||||||
|
|
||||||
if (policy == UiPopPolicy.Destroy)
|
if (policy == UiPopPolicy.Destroy)
|
||||||
{
|
{
|
||||||
Log.Debug("Destroy UI Page: {0}", top.GetType().Name);
|
top.OnExit();
|
||||||
_uiRoot.RemoveUiPage(top);
|
_uiRoot.RemoveUiPage(top);
|
||||||
// 不回收,直接销毁
|
|
||||||
}
|
}
|
||||||
else // UiPopPolicy.Cache
|
else // Suspend
|
||||||
{
|
{
|
||||||
Log.Debug("Cache UI Page: {0}", top.GetType().Name);
|
top.OnHide();
|
||||||
_uiRoot.RemoveUiPage(top);
|
|
||||||
_factory.Recycle(top); // 回收到池中
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_stack.Count > 0)
|
if (_stack.Count <= 0) return;
|
||||||
{
|
|
||||||
var next = _stack.Peek();
|
var next = _stack.Peek();
|
||||||
Log.Debug("Resume & Show page: {0}", next.GetType().Name);
|
|
||||||
next.OnResume();
|
next.OnResume();
|
||||||
next.OnShow();
|
next.OnShow();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
Log.Debug("UI stack is now empty");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 执行Clear的核心逻辑(不触发Pipeline)
|
/// 执行Clear的核心逻辑(不触发Pipeline)
|
||||||
@ -481,8 +465,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
public void Show(
|
public void Show(
|
||||||
string uiKey,
|
string uiKey,
|
||||||
UiLayer layer,
|
UiLayer layer,
|
||||||
IUiPageEnterParam? param = null,
|
IUiPageEnterParam? param = null)
|
||||||
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
|
|
||||||
{
|
{
|
||||||
if (layer == UiLayer.Page) throw new ArgumentException("Use Push() for Page layer");
|
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;
|
layerDict[uiKey] = page;
|
||||||
|
|
||||||
// 添加到UiRoot,传入层级Z-order
|
// 添加到UiRoot,传入层级Z-order
|
||||||
@ -580,14 +563,11 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
|
|||||||
{
|
{
|
||||||
_uiRoot.RemoveUiPage(page);
|
_uiRoot.RemoveUiPage(page);
|
||||||
layerDict.Remove(uiKey);
|
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
|
else
|
||||||
{
|
{
|
||||||
_uiRoot.RemoveUiPage(page);
|
Log.Debug("Suspend & Suspend UI from layer: {0}, layer={1}", uiKey, layer);
|
||||||
_factory.Recycle(page);
|
|
||||||
layerDict.Remove(uiKey);
|
|
||||||
Log.Debug("Hide & Cache UI from layer: {0}, layer={1}", uiKey, layer);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -68,6 +68,11 @@ public class CanvasItemUiPageBehavior<T>(T owner, string key) : IUiPageBehavior
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void OnResume()
|
public void OnResume()
|
||||||
{
|
{
|
||||||
|
if (owner.IsInvalidNode())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_page?.OnResume();
|
_page?.OnResume();
|
||||||
|
|
||||||
// 恢复节点的处理、物理处理和输入处理
|
// 恢复节点的处理、物理处理和输入处理
|
||||||
|
|||||||
@ -2,435 +2,62 @@ 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;
|
||||||
using GFramework.Game.Abstractions.enums;
|
|
||||||
using GFramework.Game.Abstractions.ui;
|
using GFramework.Game.Abstractions.ui;
|
||||||
using GFramework.Godot.extensions;
|
|
||||||
using Godot;
|
|
||||||
|
|
||||||
namespace GFramework.Godot.ui;
|
namespace GFramework.Godot.ui;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Godot UI工厂类,用于创建UI页面实例
|
/// Godot UI工厂类,用于创建UI页面实例。
|
||||||
/// 继承自AbstractContextUtility并实现IUiFactory接口
|
/// 继承自AbstractContextUtility并实现IUiFactory接口。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
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>
|
/// <summary>
|
||||||
/// LFU访问计数:instance -> 访问次数
|
/// UI注册表,用于管理UI场景资源。
|
||||||
/// </summary>
|
/// </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!;
|
private IGodotUiRegistry _registry = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 创建或获取UI页面实例
|
/// 根据指定的UI键创建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>
|
|
||||||
/// 仅创建新实例
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="uiKey">UI页面的唯一标识符。</param>
|
||||||
|
/// <returns>返回创建的UI页面行为实例。</returns>
|
||||||
|
/// <exception cref="InvalidCastException">
|
||||||
|
/// 当UI场景未实现IUiPageBehaviorProvider接口时抛出异常。
|
||||||
|
/// </exception>
|
||||||
public IUiPageBehavior Create(string uiKey)
|
public IUiPageBehavior Create(string uiKey)
|
||||||
{
|
{
|
||||||
|
// 从注册表中获取指定UI键对应的场景
|
||||||
var scene = _registry.Get(uiKey);
|
var scene = _registry.Get(uiKey);
|
||||||
|
|
||||||
|
// 实例化场景节点
|
||||||
var node = scene.Instantiate();
|
var node = scene.Instantiate();
|
||||||
|
|
||||||
|
// 检查节点是否实现了IUiPageBehaviorProvider接口
|
||||||
if (node is not IUiPageBehaviorProvider provider)
|
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();
|
var page = provider.GetPage();
|
||||||
|
|
||||||
// 追踪实例
|
// 记录调试日志
|
||||||
if (!_allInstances.ContainsKey(uiKey))
|
Log.Debug("Created UI instance: {0}", uiKey);
|
||||||
_allInstances[uiKey] = new HashSet<IUiPageBehavior>();
|
|
||||||
_allInstances[uiKey].Add(page);
|
|
||||||
|
|
||||||
Log.Debug("Created new UI instance: {0}", uiKey);
|
|
||||||
return page;
|
return page;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 预加载UI资源
|
/// 初始化方法,在对象初始化时调用。
|
||||||
|
/// 获取并设置UI注册表实例。
|
||||||
/// </summary>
|
/// </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()
|
protected override void OnInit()
|
||||||
{
|
{
|
||||||
_registry = this.GetUtility<IGodotUiRegistry>()!;
|
_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