using GFramework.Core.Abstractions.logging;
using GFramework.Core.extensions;
using GFramework.Core.logging;
using GFramework.Core.system;
using GFramework.Game.Abstractions.enums;
using GFramework.Game.Abstractions.ui;
namespace GFramework.Game.ui;
///
/// UI路由类,提供页面栈管理功能
///
public abstract class UiRouterBase : AbstractSystem, IUiRouter
{
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger("UiRouterBase");
///
/// 路由守卫列表
///
private readonly List _guards = new();
///
/// 层级管理(非栈层级),用于Overlay、Modal、Toast等浮层
///
private readonly Dictionary> _layers = new();
///
/// UI切换处理器管道
///
private readonly UiTransitionPipeline _pipeline = new();
///
/// 页面栈,用于管理UI页面的显示顺序
///
private readonly Stack _stack = new();
///
/// UI工厂实例,用于创建UI相关的对象
///
private IUiFactory _factory = null!;
private IUiRoot _uiRoot = null!;
///
/// 注册UI切换处理器
///
/// 处理器实例
/// 执行选项
public void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null)
{
_pipeline.RegisterHandler(handler, options);
}
///
/// 注销UI切换处理器
///
/// 处理器实例
public void UnregisterHandler(IUiTransitionHandler handler)
{
_pipeline.UnregisterHandler(handler);
}
///
/// 绑定UI根节点
///
public void BindRoot(IUiRoot root)
{
_uiRoot = root;
Log.Debug("Bind UI Root: {0}", root.GetType().Name);
}
///
/// 将指定的UI界面压入路由栈,显示新的UI界面
///
/// UI界面的唯一标识符
/// 进入界面的参数,可为空
/// 界面切换策略,默认为Exclusive(独占)
/// 实例管理策略,默认为Reuse(复用)
public void Push(string uiKey, IUiPageEnterParam? param = null,
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
{
if (IsTop(uiKey))
{
Log.Warn("Push ignored: UI already on top: {0}", uiKey);
return;
}
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
);
BeforeChange(@event);
DoPushPageInternal(uiKey, param, policy, instancePolicy);
AfterChange(@event);
}
///
/// 将已存在的UI页面压入栈顶并显示
///
/// 已创建的UI页面行为实例
/// 页面进入参数,可为空
/// 页面切换策略
public void Push(
IUiPageBehavior page,
IUiPageEnterParam? param = null,
UiTransitionPolicy policy = UiTransitionPolicy.Exclusive
)
{
var uiKey = page.View.GetType().Name;
if (IsTop(uiKey))
{
Log.Warn("Push ignored: UI already on top: {0}", uiKey);
return;
}
var @event = CreateEvent(uiKey, UiTransitionType.Push, policy, param);
Log.Debug(
"Push existing UI Page: key={0}, policy={1}, stackBefore={2}",
uiKey, policy, _stack.Count
);
BeforeChange(@event);
DoPushPageInternal(page, param, policy);
AfterChange(@event);
}
///
/// 弹出栈顶页面并根据策略处理页面
///
/// 弹出策略,默认为销毁策略
public void Pop(UiPopPolicy policy = UiPopPolicy.Destroy)
{
if (_stack.Count == 0)
{
Log.Debug("Pop ignored: stack is empty");
return;
}
var leavingUiKey = _stack.Peek().Key;
if (!ExecuteLeaveGuardsAsync(leavingUiKey).GetAwaiter().GetResult())
{
Log.Warn("Pop blocked by guard: {0}", leavingUiKey);
return;
}
// ⚠️ 注意:nextUiKey 现在是可选的
var nextUiKey = _stack.Count > 1
? _stack.ElementAt(1).Key
: null;
var @event = CreateEvent(
nextUiKey,
type: UiTransitionType.Pop
);
BeforeChange(@event);
DoPopInternal(policy);
AfterChange(@event);
}
///
/// 替换当前所有页面为新页面(基于uiKey)
///
/// 新UI页面标识符
/// 页面进入参数,可为空
/// 弹出页面时的销毁策略,默认为销毁
/// 推入页面时的过渡策略,默认为独占
/// 实例管理策略
public void Replace(
string uiKey,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
{
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
);
BeforeChange(@event);
// 使用内部方法清空栈,避免触发额外的Pipeline
DoClearInternal(popPolicy);
// 使用工厂的增强方法获取实例
var page = _factory.GetOrCreate(uiKey, instancePolicy);
Log.Debug("Get/Create UI Page instance for Replace: {0}", page.GetType().Name);
DoPushPageInternal(page, param, pushPolicy);
AfterChange(@event);
}
///
/// 替换当前所有页面为已存在的页面(基于实例)
///
/// 已创建的UI页面行为实例
/// 页面进入参数,可为空
/// 弹出页面时的销毁策略,默认为销毁
/// 推入页面时的过渡策略,默认为独占
public void Replace(
IUiPageBehavior page,
IUiPageEnterParam? param = null,
UiPopPolicy popPolicy = UiPopPolicy.Destroy,
UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive)
{
var uiKey = page.Key;
var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param);
Log.Debug(
"Replace UI Stack with existing page: key={0}, popPolicy={1}, pushPolicy={2}",
uiKey, popPolicy, pushPolicy
);
BeforeChange(@event);
// 清空栈
DoClearInternal(popPolicy);
Log.Debug("Use existing UI Page instance for Replace: {0}", page.GetType().Name);
DoPushPageInternal(page, param, pushPolicy);
AfterChange(@event);
}
///
/// 清空所有页面栈中的页面
///
public void Clear()
{
var @event = CreateEvent(string.Empty, UiTransitionType.Clear);
Log.Debug("Clear UI Stack, stackCount={0}", _stack.Count);
BeforeChange(@event);
// 使用内部方法,避免触发额外的Pipeline
DoClearInternal(UiPopPolicy.Destroy);
AfterChange(@event);
}
///
/// 获取页面栈顶元素的键值,但不移除该元素
///
/// 如果页面栈为空则返回空字符串,否则返回栈顶元素的键值
public string PeekKey()
{
return _stack.Count == 0 ? string.Empty : _stack.Peek().Key;
}
///
/// 获取页面栈顶元素,但不移除该元素
///
/// 返回栈顶的IUiPageBehavior元素
public IUiPageBehavior Peek()
{
return _stack.Peek();
}
///
/// 判断栈顶元素是否指定的UI类型
///
/// 要比较的UI类型名称
/// 如果栈为空或栈顶元素类型不匹配则返回false,否则返回true
public bool IsTop(string uiKey)
{
return _stack.Count != 0 && _stack.Peek().Key.Equals(uiKey);
}
///
/// 判断栈中是否包含指定类型的UI元素
///
/// 要查找的UI类型名称
/// 如果栈中存在指定类型的UI元素则返回true,否则返回false
public bool Contains(string uiKey)
{
return _stack.Any(p => p.Key.Equals(uiKey));
}
///
/// 获取栈中元素的数量
///
public int Count => _stack.Count;
///
/// 初始化方法,在页面初始化时获取UI工厂实例
///
protected override void OnInit()
{
_factory = this.GetUtility()!;
Log.Debug("UiRouterBase initialized. Factory={0}", _factory.GetType().Name);
RegisterHandlers();
}
///
/// 注册默认的UI切换处理器
///
protected abstract void RegisterHandlers();
///
/// 创建UI切换事件
///
private UiTransitionEvent CreateEvent(
string? toUiKey,
UiTransitionType type,
UiTransitionPolicy? policy = null,
IUiPageEnterParam? param = null
)
{
return new UiTransitionEvent
{
FromUiKey = PeekKey(),
ToUiKey = toUiKey,
TransitionType = type,
Policy = policy ?? UiTransitionPolicy.Exclusive,
EnterParam = param
};
}
///
/// 执行UI切换前的Handler(阻塞)
///
private void BeforeChange(UiTransitionEvent @event)
{
Log.Debug("BeforeChange phases started: {0}", @event.TransitionType);
_pipeline.ExecuteAsync(@event, UITransitionPhases.BeforeChange).GetAwaiter().GetResult();
Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType);
}
///
/// 执行UI切换后的Handler(不阻塞)
///
private void AfterChange(UiTransitionEvent @event)
{
Log.Debug("AfterChange phases started: {0}", @event.TransitionType);
_ = Task.Run(async () =>
{
try
{
await _pipeline.ExecuteAsync(@event, UITransitionPhases.AfterChange).ConfigureAwait(false);
Log.Debug("AfterChange phases completed: {0}", @event.TransitionType);
}
catch (Exception ex)
{
Log.Error("AfterChange phases failed: {0}, Error: {1}", @event.TransitionType, ex.Message);
}
});
}
///
/// 执行Push页面的核心逻辑(基于 uiKey)
///
private void DoPushPageInternal(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy,
UiInstancePolicy instancePolicy)
{
// 执行进入守卫
if (!ExecuteEnterGuardsAsync(uiKey, param).GetAwaiter().GetResult())
{
Log.Warn("Push blocked by guard: {0}", uiKey);
return;
}
// 使用工厂的增强方法获取实例
var page = _factory.GetOrCreate(uiKey, instancePolicy);
Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name);
DoPushPageInternal(page, param, policy);
}
///
/// 执行Push页面的核心逻辑(基于 page)
///
private void DoPushPageInternal(IUiPageBehavior page, IUiPageEnterParam? param, UiTransitionPolicy policy)
{
// 1. 处理当前栈顶页面
if (_stack.Count > 0)
{
var current = _stack.Peek();
Log.Debug("Pause current page: {0}", current.View.GetType().Name);
current.OnPause();
if (policy == UiTransitionPolicy.Exclusive)
{
Log.Debug("Hide current page (Exclusive): {0}", current.View.GetType().Name);
current.OnHide();
}
}
// 2. 将新页面添加到UiRoot
Log.Debug("Add page to UiRoot: {0}", page.View.GetType().Name);
_uiRoot.AddUiPage(page);
// 3. 压入栈
_stack.Push(page);
// 4. 触发页面生命周期
Log.Debug(
"Enter & Show page: {0}, stackAfter={1}",
page.View.GetType().Name, _stack.Count
);
page.OnEnter(param);
page.OnShow();
}
///
/// 执行Pop的核心逻辑(不触发Pipeline)
///
private void DoPopInternal(UiPopPolicy policy)
{
if (_stack.Count == 0)
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);
_uiRoot.RemoveUiPage(top);
// 不回收,直接销毁
}
else // UiPopPolicy.Cache
{
Log.Debug("Cache UI Page: {0}", top.GetType().Name);
_uiRoot.RemoveUiPage(top);
_factory.Recycle(top); // 回收到池中
}
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");
}
}
///
/// 执行Clear的核心逻辑(不触发Pipeline)
///
/// UI弹出策略
private void DoClearInternal(UiPopPolicy policy)
{
Log.Debug("Clear UI Stack internal, count={0}", _stack.Count);
// 循环执行弹出操作直到栈为空
while (_stack.Count > 0)
DoPopInternal(policy);
}
#region 层级管理
///
/// 在指定层级显示UI(非栈管理)
///
public void Show(
string uiKey,
UiLayer layer,
IUiPageEnterParam? param = null,
UiInstancePolicy instancePolicy = UiInstancePolicy.Reuse)
{
if (layer == UiLayer.Page)
{
throw new ArgumentException("Use Push() for Page layer");
}
// 初始化层级字典
if (!_layers.ContainsKey(layer))
_layers[layer] = new Dictionary();
var layerDict = _layers[layer];
// 检查是否已存在
if (layerDict.TryGetValue(uiKey, out var existing))
{
Log.Debug("UI already visible in layer: {0}, layer={1}", uiKey, layer);
existing.OnEnter(param);
existing.OnShow();
return;
}
// 获取或创建实例
var page = _factory.GetOrCreate(uiKey, instancePolicy);
layerDict[uiKey] = page;
// 添加到UiRoot,传入层级Z-order
_uiRoot.AddUiPage(page, layer);
page.OnEnter(param);
page.OnShow();
Log.Debug("Show UI in layer: {0}, layer={1}", uiKey, layer);
}
///
/// 在指定层级显示UI(基于实例)
///
public void Show(IUiPageBehavior page, UiLayer layer)
{
if (layer == UiLayer.Page)
throw new ArgumentException("Use Push() for Page layer");
var uiKey = page.Key;
if (!_layers.ContainsKey(layer))
_layers[layer] = new Dictionary();
_layers[layer][uiKey] = page;
_uiRoot.AddUiPage(page, layer);
page.OnShow();
Log.Debug("Show existing UI instance in layer: {0}, layer={1}", uiKey, layer);
}
///
/// 隐藏指定层级的UI
///
public void Hide(string uiKey, UiLayer layer, bool destroy = false)
{
if (!_layers.TryGetValue(layer, out var layerDict))
return;
if (!layerDict.TryGetValue(uiKey, out var page))
return;
page.OnExit();
page.OnHide();
if (destroy)
{
_uiRoot.RemoveUiPage(page);
layerDict.Remove(uiKey);
Log.Debug("Hide & 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);
}
}
///
/// 清空指定层级的所有UI
///
public void ClearLayer(UiLayer layer, bool destroy = false)
{
if (!_layers.TryGetValue(layer, out var layerDict))
return;
var keys = layerDict.Keys.ToArray();
foreach (var key in keys)
{
Hide(key, layer, destroy);
}
Log.Debug("Cleared layer: {0}, destroyed={1}", layer, destroy);
}
///
/// 获取指定层级的UI实例
///
public IUiPageBehavior? GetFromLayer(string uiKey, UiLayer layer)
{
return _layers.TryGetValue(layer, out var layerDict) &&
layerDict.TryGetValue(uiKey, out var page)
? page
: null;
}
///
/// 判断指定层级是否有UI显示
///
public bool HasVisibleInLayer(UiLayer layer)
{
return _layers.TryGetValue(layer, out var layerDict) && layerDict.Count > 0;
}
#endregion
#region 路由守卫
///
/// 注册路由守卫
///
public void AddGuard(IUiRouteGuard guard)
{
ArgumentNullException.ThrowIfNull(guard);
if (_guards.Contains(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);
}
///
/// 注册路由守卫(泛型方法)
///
public void AddGuard() where T : IUiRouteGuard, new()
{
var guard = new T();
AddGuard(guard);
}
///
/// 移除路由守卫
///
public void RemoveGuard(IUiRouteGuard guard)
{
ArgumentNullException.ThrowIfNull(guard);
if (_guards.Remove(guard))
{
Log.Debug("Guard removed: {0}", guard.GetType().Name);
}
}
///
/// 执行进入守卫
///
private async Task ExecuteEnterGuardsAsync(string uiKey, IUiPageEnterParam? param)
{
foreach (var guard in _guards)
{
try
{
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;
}
}
catch (Exception ex)
{
Log.Error("Enter guard {0} failed: {1}", guard.GetType().Name, ex.Message);
if (guard.CanInterrupt)
{
return false;
}
}
}
return true;
}
///
/// 执行离开守卫
///
private async Task 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
}