feat(ui): 实现UI缓存淘汰策略和移除动画相关功能

- 新增CacheEvictionPolicy枚举定义LRU和LFU缓存淘汰策略
- 在GodotUiFactory中实现缓存淘汰机制支持LRU/LFU策略
- 将IUiCacheStatistics接口从IUiFactory中分离到独立文件
- 移除UiAnimationPolicy类及相关动画策略参数配置
- 移除GodotUiTransition类中的UI过渡动画实现
- 移除UiTransitionAnimation枚举类型
- 更新UiRouterBase中路由方法移除动画策略参数
- 重构路由守卫注册方法位置优化代码结构
- 更新UiCacheConfig配置类适配新的缓存策略枚举值
-[skip ci]
This commit is contained in:
GeWuYou 2026-01-20 12:27:22 +08:00
parent ad061bba46
commit 3362d9456d
10 changed files with 203 additions and 529 deletions

View File

@ -0,0 +1,17 @@
namespace GFramework.Game.Abstractions.enums;
/// <summary>
/// 缓存淘汰策略枚举
/// </summary>
public enum CacheEvictionPolicy
{
/// <summary>
/// 最近最少使用
/// </summary>
Lru,
/// <summary>
/// 最少使用频率
/// </summary>
Lfu,
}

View File

@ -1,48 +0,0 @@
namespace GFramework.Game.Abstractions.enums;
/// <summary>
/// UI过渡动画类型枚举
/// 定义UI切换时支持的动画效果
/// </summary>
public enum UiTransitionAnimation
{
/// <summary>
/// 无动画
/// </summary>
None,
/// <summary>
/// 淡入淡出动画
/// </summary>
Fade,
/// <summary>
/// 从右侧滑入
/// </summary>
SlideLeft,
/// <summary>
/// 从左侧滑入
/// </summary>
SlideRight,
/// <summary>
/// 从下方滑入
/// </summary>
SlideUp,
/// <summary>
/// 从上方滑入
/// </summary>
SlideDown,
/// <summary>
/// 缩放动画
/// </summary>
Scale,
/// <summary>
/// 自定义动画(需要提供自定义的 IUiTransition 实现)
/// </summary>
Custom
}

View File

@ -0,0 +1,34 @@
using System;
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; }
}

View File

@ -4,37 +4,6 @@ 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>

View File

@ -26,9 +26,8 @@ public interface IUiRouter : ISystem
/// <param name="param">进入界面的参数,可为空</param>
/// <param name="policy">界面切换策略默认为Exclusive独占</param>
/// <param name="instancePolicy">实例管理策略默认为Reuse复用</param>
/// <param name="animationPolicy">动画策略,可为空</param>
void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse, UiAnimationPolicy? animationPolicy = null);
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
/// <summary>
@ -38,9 +37,8 @@ public interface IUiRouter : ISystem
/// <param name="page">已创建的UI页面行为实例</param>
/// <param name="param">进入界面的参数,可为空</param>
/// <param name="policy">界面切换策略,默认为Exclusive(独占)</param>
/// <param name="animationPolicy">动画策略,可为空</param>
void Push(IUiPageBehavior page, IUiPageEnterParam? param = null,
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive, UiAnimationPolicy? animationPolicy = null);
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive);
/// <summary>
@ -57,14 +55,12 @@ public interface IUiRouter : ISystem
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
/// <param name="instancePolicy">实例管理策略</param>
/// <param name="animationPolicy">动画策略,可为空</param>
public void Replace(
string uiKey,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse,
UiAnimationPolicy? animationPolicy = null);
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse);
/// <summary>
/// 替换当前所有页面为已存在的页面(基于实例)
@ -73,13 +69,11 @@ public interface IUiRouter : ISystem
/// <param name="param">页面进入参数,可为空</param>
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
/// <param name="animationPolicy">动画策略,可为空</param>
public void Replace(
IUiPageBehavior page,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
UiAnimationPolicy? animationPolicy = null);
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive);
/// <summary>
/// 清空所有UI界面重置路由状态
/// </summary>
@ -129,17 +123,16 @@ public interface IUiRouter : ISystem
/// <param name="guard">守卫实例</param>
void AddGuard(IUiRouteGuard guard);
/// <summary>
/// 注册路由守卫(泛型方法)
/// </summary>
/// <typeparam name="T">守卫类型,必须实现 IUiRouteGuard 且有无参构造函数</typeparam>
void AddGuard<T>() where T : IUiRouteGuard, new();
/// <summary>
/// 移除路由守卫
/// </summary>
/// <param name="guard">守卫实例</param>
void RemoveGuard(IUiRouteGuard guard);
/// <summary>
/// 注册路由守卫(泛型方法)
/// </summary>
/// <typeparam name="T">守卫类型,必须实现 IUiRouteGuard 且有无参构造函数</typeparam>
void AddGuard<T>() where T : IUiRouteGuard, new();
#endregion
}

View File

@ -1,91 +0,0 @@
using GFramework.Game.Abstractions.enums;
namespace GFramework.Game.Abstractions.ui;
/// <summary>
/// UI动画策略配置
/// 用于配置UI过渡动画的行为
/// </summary>
public class UiAnimationPolicy
{
/// <summary>
/// 动画类型
/// </summary>
public UiTransitionAnimation Animation { get; set; } = UiTransitionAnimation.None;
/// <summary>
/// 动画持续时间(秒)
/// </summary>
public float Duration { get; set; } = 0.3f;
/// <summary>
/// 是否阻塞UI切换等待动画完成
/// </summary>
public bool BlockTransition { get; set; } = false;
/// <summary>
/// 自定义动画实现(仅当 Animation 为 Custom 时使用)
/// </summary>
public IUiTransition? CustomTransition { get; set; }
/// <summary>
/// 缓动函数(可选,用于调整动画曲线)
/// </summary>
public EasingFunction Easing { get; set; } = EasingFunction.EaseInOut;
/// <summary>
/// 创建默认策略(无动画)
/// </summary>
public static UiAnimationPolicy None => new UiAnimationPolicy { Animation = UiTransitionAnimation.None };
/// <summary>
/// 创建淡入淡出策略
/// </summary>
/// <param name="duration">持续时间</param>
/// <param name="block">是否阻塞</param>
public static UiAnimationPolicy Fade(float duration = 0.3f, bool block = false)
=> new UiAnimationPolicy { Animation = UiTransitionAnimation.Fade, Duration = duration, BlockTransition = block };
/// <summary>
/// 创建滑入策略
/// </summary>
/// <param name="direction">滑动方向</param>
/// <param name="duration">持续时间</param>
/// <param name="block">是否阻塞</param>
public static UiAnimationPolicy Slide(UiTransitionAnimation direction, float duration = 0.3f, bool block = false)
=> new UiAnimationPolicy { Animation = direction, Duration = duration, BlockTransition = block };
/// <summary>
/// 创建缩放策略
/// </summary>
/// <param name="duration">持续时间</param>
/// <param name="block">是否阻塞</param>
public static UiAnimationPolicy Scale(float duration = 0.3f, bool block = false)
=> new UiAnimationPolicy { Animation = UiTransitionAnimation.Scale, Duration = duration, BlockTransition = block };
}
/// <summary>
/// 缓动函数枚举
/// </summary>
public enum EasingFunction
{
/// <summary>
/// 线性
/// </summary>
Linear,
/// <summary>
/// 缓入
/// </summary>
EaseIn,
/// <summary>
/// 缓出
/// </summary>
EaseOut,
/// <summary>
/// 缓入缓出
/// </summary>
EaseInOut
}

View File

@ -1,4 +1,5 @@
using System;
using GFramework.Game.Abstractions.enums;
namespace GFramework.Game.Abstractions.ui;
@ -16,20 +17,20 @@ public class UiCacheConfig
/// <summary>
/// 缓存淘汰策略
/// </summary>
public CacheEvictionPolicy EvictionPolicy { get; set; } = CacheEvictionPolicy.LRU;
public CacheEvictionPolicy EvictionPolicy { get; set; } = CacheEvictionPolicy.Lru;
/// <summary>
/// 访问后过期时间可选null 表示不启用)
/// </summary>
public TimeSpan? ExpireAfterAccess { get; set; } = null;
public TimeSpan? ExpireAfterAccess { get; set; }
/// <summary>
/// 创建默认配置LRU 策略,最大 10 个实例)
/// </summary>
public static UiCacheConfig Default => new UiCacheConfig
public static UiCacheConfig Default => new()
{
MaxCacheSize = 10,
EvictionPolicy = CacheEvictionPolicy.LRU,
EvictionPolicy = CacheEvictionPolicy.Lru,
ExpireAfterAccess = null
};
@ -39,10 +40,10 @@ public class UiCacheConfig
/// <param name="maxSize">最大缓存数量</param>
/// <param name="expireAfter">访问后过期时间</param>
public static UiCacheConfig Lru(int maxSize = 10, TimeSpan? expireAfter = null)
=> new UiCacheConfig
=> new()
{
MaxCacheSize = maxSize,
EvictionPolicy = CacheEvictionPolicy.LRU,
EvictionPolicy = CacheEvictionPolicy.Lru,
ExpireAfterAccess = expireAfter
};
@ -52,26 +53,10 @@ public class UiCacheConfig
/// <param name="maxSize">最大缓存数量</param>
/// <param name="expireAfter">访问后过期时间</param>
public static UiCacheConfig Lfu(int maxSize = 10, TimeSpan? expireAfter = null)
=> new UiCacheConfig
=> new()
{
MaxCacheSize = maxSize,
EvictionPolicy = CacheEvictionPolicy.LFU,
EvictionPolicy = CacheEvictionPolicy.Lfu,
ExpireAfterAccess = expireAfter
};
}
/// <summary>
/// 缓存淘汰策略枚举
/// </summary>
public enum CacheEvictionPolicy
{
/// <summary>
/// 最近最少使用
/// </summary>
LRU,
/// <summary>
/// 最少使用频率
/// </summary>
LFU
}
}

View File

@ -4,7 +4,6 @@ using GFramework.Core.logging;
using GFramework.Core.system;
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using System.Linq;
namespace GFramework.Game.ui;
@ -69,7 +68,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
_uiRoot = root;
Log.Debug("Bind UI Root: {0}", root.GetType().Name);
}
/// <summary>
/// 将指定的UI界面压入路由栈显示新的UI界面
@ -78,9 +77,9 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <param name="param">进入界面的参数,可为空</param>
/// <param name="policy">界面切换策略默认为Exclusive独占</param>
/// <param name="instancePolicy">实例管理策略默认为Reuse复用</param>
/// <param name="animationPolicy">动画策略,可为空</param>
public void Push(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse, UiAnimationPolicy? animationPolicy = null)
public void Push(string uiKey, IUiPageEnterParam? param = null,
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
{
if (IsTop(uiKey))
{
@ -89,7 +88,6 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
}
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
@event.Set("AnimationPolicy", animationPolicy);
Log.Debug(
"Push UI Page: key={0}, policy={1}, instancePolicy={2}, stackBefore={3}",
@ -97,7 +95,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
);
BeforeChange(@event);
DoPushPageInternal(uiKey, param, policy, instancePolicy, animationPolicy);
DoPushPageInternal(uiKey, param, policy, instancePolicy);
AfterChange(@event);
}
@ -107,12 +105,10 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <param name="page">已创建的UI页面行为实例</param>
/// <param name="param">页面进入参数,可为空</param>
/// <param name="policy">页面切换策略</param>
/// <param name="animationPolicy">动画策略,可为空</param>
public void Push(
IUiPageBehavior page,
IUiPageEnterParam? param = null,
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
UiAnimationPolicy? animationPolicy = null
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive
)
{
var uiKey = page.View.GetType().Name;
@ -124,7 +120,6 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
}
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
@event.Set("AnimationPolicy", animationPolicy);
Log.Debug(
"Push existing UI Page: key={0}, policy={1}, stackBefore={2}",
@ -177,18 +172,14 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
/// <param name="instancePolicy">实例管理策略</param>
/// <param name="animationPolicy">动画策略,可为空</param>
public void Replace(
string uiKey,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse,
UiAnimationPolicy? animationPolicy = null)
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
{
var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param);
@event.Set("AnimationPolicy", animationPolicy);
Log.Debug(
"Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}, instancePolicy={3}",
uiKey, popPolicy, pushPolicy, instancePolicy
@ -207,6 +198,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
AfterChange(@event);
}
/// <summary>
/// 替换当前所有页面为已存在的页面(基于实例)
/// </summary>
@ -214,17 +206,14 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <param name="param">页面进入参数,可为空</param>
/// <param name="popPolicy">弹出页面时的销毁策略,默认为销毁</param>
/// <param name="pushPolicy">推入页面时的过渡策略,默认为独占</param>
/// <param name="animationPolicy">动画策略,可为空</param>
public void Replace(
IUiPageBehavior page,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
UiAnimationPolicy? animationPolicy = null)
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive)
{
var uiKey = page.Key;
var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param);
@event.Set("AnimationPolicy", animationPolicy);
Log.Debug(
"Replace UI Stack with existing page: key={0}, popPolicy={1}, pushPolicy={2}",
@ -373,7 +362,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// 执行Push页面的核心逻辑基于 uiKey
/// </summary>
private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy,
UiInstancePolicy instancePolicy, UiAnimationPolicy? animationPolicy)
UiInstancePolicy instancePolicy)
{
// 执行进入守卫
if (!ExecuteEnterGuardsAsync(uiKey, param).GetAwaiter().GetResult())
@ -486,11 +475,11 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// </summary>
public void Show(
string uiKey,
Game.Abstractions.enums.UiLayer layer,
UiLayer layer,
IUiPageEnterParam? param = null,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
{
if (layer == Game.Abstractions.enums.UiLayer.Page)
if (layer == UiLayer.Page)
{
throw new ArgumentException("Use Push() for Page layer");
}
@ -546,7 +535,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <summary>
/// 隐藏指定层级的UI
/// </summary>
public void Hide(string uiKey, Game.Abstractions.enums.UiLayer layer, bool destroy = false)
public void Hide(string uiKey, UiLayer layer, bool destroy = false)
{
if (!_layers.TryGetValue(layer, out var layerDict))
return;
@ -575,7 +564,7 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
/// <summary>
/// 清空指定层级的所有UI
/// </summary>
public void ClearLayer(Game.Abstractions.enums.UiLayer layer, bool destroy = false)
public void ClearLayer(UiLayer layer, bool destroy = false)
{
if (!_layers.TryGetValue(layer, out var layerDict))
return;
@ -612,121 +601,120 @@ public abstract class UiRouterBase : AbstractSystem, IUiRouter
#region
/// <summary>
/// 注册路由守卫
/// </summary>
public void AddGuard(IUiRouteGuard guard)
{
ArgumentNullException.ThrowIfNull(guard);
if (_guards.Contains(guard))
/// <summary>
/// 注册路由守卫
/// </summary>
public void AddGuard(IUiRouteGuard 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);
}
ArgumentNullException.ThrowIfNull(guard);
/// <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
if (_guards.Contains(guard))
{
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;
}
Log.Debug("Guard already registered: {0}", guard.GetType().Name);
return;
}
catch (Exception ex)
_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 AddGuard<T>() where T : IUiRouteGuard, new()
{
var guard = new T();
AddGuard(guard);
}
/// <summary>
/// 移除路由守卫
/// </summary>
public void RemoveGuard(IUiRouteGuard guard)
{
ArgumentNullException.ThrowIfNull(guard);
if (_guards.Remove(guard))
{
Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message);
if (guard.CanInterrupt)
{
return false;
}
Log.Debug("Guard removed: {0}", guard.GetType().Name);
}
}
return true;
}
/// <summary>
/// 执行离开守卫
/// </summary>
private async Task<bool> ExecuteLeaveGuardsAsync(string uiKey)
{
foreach (var guard in _guards)
/// <summary>
/// 执行进入守卫
/// </summary>
private async Task<bool> ExecuteEnterGuardsAsync(string uiKey, IUiPageEnterParam? param)
{
try
foreach (var guard in _guards)
{
Log.Debug("Executing leave guard: {0} for {1}", guard.GetType().Name, uiKey);
var canLeave = await guard.CanLeaveAsync(uiKey);
if (!canLeave)
try
{
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;
}
Log.Debug("Executing enter guard: {0} for {1}", guard.GetType().Name, uiKey);
var canEnter = await guard.CanEnterAsync(uiKey, param);
#endregion
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
}

View File

@ -1,8 +1,8 @@
using System;
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;
@ -18,7 +18,7 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
/// <summary>
/// 缓存统计信息实现类
/// </summary>
private class CacheStatisticsInfo : IUiCacheStatistics
private sealed class CacheStatisticsInfo : IUiCacheStatistics
{
public int CacheSize { get; set; }
public int HitCount { get; set; }
@ -381,27 +381,25 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
{
var config = GetCacheConfig(uiKey);
var currentSize = _cachedInstances.TryGetValue(uiKey, out var queue) ? queue.Count : 0;
if (currentSize > config.MaxCacheSize)
if (currentSize <= config.MaxCacheSize) return;
var toEvict = currentSize - config.MaxCacheSize;
for (var i = 0; i < toEvict; i++)
{
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);
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)
private void EvictLru(string uiKey)
{
if (!_accessTimeQueue.TryGetValue(uiKey, out var timeQueue) || timeQueue.Count == 0)
return;
@ -437,7 +435,7 @@ public class GodotUiFactory : AbstractContextUtility, IUiFactory
/// <summary>
/// LFU淘汰策略
/// </summary>
private void EvictLFU(string uiKey)
private void EvictLfu(string uiKey)
{
if (!_cachedInstances.TryGetValue(uiKey, out var queue) || queue.Count == 0)
return;

View File

@ -1,171 +0,0 @@
using System;
using System.Threading.Tasks;
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
using Godot;
namespace GFramework.Godot.ui;
/// <summary>
/// Godot平台的UI过渡动画实现
/// 支持多种预定义动画效果
/// </summary>
public class GodotUiTransition : IUiTransition
{
private readonly UiTransitionAnimation _animation;
/// <summary>
/// 创建过渡动画实例
/// </summary>
/// <param name="animation">动画类型</param>
public GodotUiTransition(UiTransitionAnimation animation)
{
_animation = animation;
}
/// <summary>
/// 播放进入动画
/// </summary>
public async Task PlayEnterAsync(IUiPageBehavior page)
{
var node = page.View as Node;
if (node == null)
return;
switch (_animation)
{
case UiTransitionAnimation.Fade:
await PlayFadeEnterAsync(node);
break;
case UiTransitionAnimation.Scale:
await PlayScaleEnterAsync(node);
break;
case UiTransitionAnimation.SlideLeft:
case UiTransitionAnimation.SlideRight:
case UiTransitionAnimation.SlideUp:
case UiTransitionAnimation.SlideDown:
await PlaySlideEnterAsync(node, _animation);
break;
case UiTransitionAnimation.None:
default:
break;
}
}
/// <summary>
/// 播放退出动画
/// </summary>
public async Task PlayExitAsync(IUiPageBehavior page)
{
var node = page.View as Node;
if (node == null)
return;
switch (_animation)
{
case UiTransitionAnimation.Fade:
await PlayFadeExitAsync(node);
break;
case UiTransitionAnimation.Scale:
await PlayScaleExitAsync(node);
break;
case UiTransitionAnimation.SlideLeft:
case UiTransitionAnimation.SlideRight:
case UiTransitionAnimation.SlideUp:
case UiTransitionAnimation.SlideDown:
await PlaySlideExitAsync(node, _animation);
break;
case UiTransitionAnimation.None:
default:
break;
}
}
private static async Task PlayFadeEnterAsync(Node node)
{
if (node is CanvasItem canvasItem)
{
canvasItem.Modulate = new Color(1,1, 1, 0);
canvasItem.Visible = true;
await Task.Delay(300);
canvasItem.Modulate = new Color(1,1, 1, 1);
}
}
private static async Task PlayFadeExitAsync(Node node)
{
if (node is CanvasItem canvasItem)
{
await Task.Delay(300);
canvasItem.Modulate = new Color(1,1, 1, 0);
}
}
private static async Task PlayScaleEnterAsync(Node node)
{
if (node is Control control)
{
control.Scale = Vector2.Zero;
control.PivotOffset = control.Size / 2;
await Task.Delay(300);
control.Scale = Vector2.One;
}
}
private static async Task PlayScaleExitAsync(Node node)
{
if (node is Control control)
{
control.PivotOffset = control.Size / 2;
await Task.Delay(300);
control.Scale = Vector2.Zero;
}
}
private static async Task PlaySlideEnterAsync(Node node, UiTransitionAnimation direction)
{
if (node is Control control)
{
var screenPos = control.GetViewportRect().Size;
var offset = GetSlideOffset(direction, screenPos);
control.Position += offset;
await Task.Delay(300);
control.Position -= offset;
}
}
private static async Task PlaySlideExitAsync(Node node, UiTransitionAnimation direction)
{
if (node is Control control)
{
var screenPos = control.GetViewportRect().Size;
var offset = GetSlideExitOffset(direction, screenPos);
await Task.Delay(300);
control.Position += offset;
}
}
private static Vector2 GetSlideOffset(UiTransitionAnimation direction, Vector2 screenPos)
{
return direction switch
{
UiTransitionAnimation.SlideLeft => Vector2.Right * screenPos.X,
UiTransitionAnimation.SlideRight => Vector2.Left * screenPos.X,
UiTransitionAnimation.SlideUp => Vector2.Down * screenPos.Y,
UiTransitionAnimation.SlideDown => Vector2.Up * screenPos.Y,
_ => Vector2.Zero
};
}
private static Vector2 GetSlideExitOffset(UiTransitionAnimation direction, Vector2 screenPos)
{
return direction switch
{
UiTransitionAnimation.SlideLeft => Vector2.Left * screenPos.X,
UiTransitionAnimation.SlideRight => Vector2.Right * screenPos.X,
UiTransitionAnimation.SlideUp => Vector2.Up * screenPos.Y,
UiTransitionAnimation.SlideDown => Vector2.Down * screenPos.Y,
_ => Vector2.Zero
};
}
}