mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
- 移除多余using语句和空行,统一代码缩进格式 - 优化注释文档中的缩进和对齐格式 - 简化条件语句和方法实现,提升代码可读性 - 重构协程系统相关类的字段和方法定义格式 - 更新架构服务中容器访问方式的实现 - 调整异步操作类的属性和方法组织结构 - [skip ci]
436 lines
13 KiB
C#
436 lines
13 KiB
C#
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接口
|
||
/// </summary>
|
||
public class GodotUiFactory : AbstractContextUtility, IUiFactory
|
||
{
|
||
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("GodotUiFactory");
|
||
|
||
/// <summary>
|
||
/// LFU访问计数:instance -> 访问次数
|
||
/// </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>
|
||
/// 仅创建新实例
|
||
/// </summary>
|
||
public IUiPageBehavior Create(string uiKey)
|
||
{
|
||
var scene = _registry.Get(uiKey);
|
||
var node = scene.Instantiate();
|
||
|
||
if (node is not IUiPageBehaviorProvider provider)
|
||
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);
|
||
return page;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 预加载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
|
||
} |