GFramework/GFramework.Godot/ui/GodotUiFactory.cs
GeWuYou b49079de3e style(coding-style): 统一代码风格并优化文档格式
- 移除多余using语句和空行,统一代码缩进格式
- 优化注释文档中的缩进和对齐格式
- 简化条件语句和方法实现,提升代码可读性
- 重构协程系统相关类的字段和方法定义格式
- 更新架构服务中容器访问方式的实现
- 调整异步操作类的属性和方法组织结构
- [skip ci]
2026-01-27 20:30:04 +08:00

436 lines
13 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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
}