using GFramework.Core.Abstractions.Logging; using GFramework.Core.Extensions; using GFramework.Core.Logging; using GFramework.Core.Systems; using GFramework.Game.Abstractions.Enums; using GFramework.Game.Abstractions.UI; namespace GFramework.Game.UI; /// /// UI路由基类,提供页面栈管理和层级UI管理功能 /// 负责UI页面的导航、显示、隐藏以及生命周期管理 /// public abstract class UiRouterBase : AbstractSystem, IUiRouter { private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(UiRouterBase)); /// /// 路由守卫列表,用于控制UI页面的进入和离开 /// private readonly List _guards = new(); /// /// 层级管理字典(非栈层级),用于管理Overlay、Modal、Toast等浮层UI /// Key: UiLayer枚举值, Value: InstanceId到PageBehavior的映射字典 /// private readonly Dictionary> _layers = new(); /// /// UI切换处理器管道,用于执行UI过渡动画和逻辑 /// private readonly UiTransitionPipeline _pipeline = new(); /// /// 页面栈,用于管理UI页面的显示顺序和导航历史 /// private readonly Stack _stack = new(); /// /// UI工厂实例,用于创建UI页面和相关对象 /// private IUiFactory _factory = null!; /// /// 实例ID计数器,用于生成唯一的UI实例标识符 /// private int _instanceCounter; /// /// UI根节点引用,用于添加和移除UI页面 /// private IUiRoot _uiRoot = null!; /// /// 注册UI切换处理器 /// /// UI切换处理器实例 /// 处理器选项配置 public void RegisterHandler(IUiTransitionHandler handler, UiTransitionHandlerOptions? options = null) { _pipeline.RegisterHandler(handler, options); } /// /// 注销UI切换处理器 /// /// 要注销的UI切换处理器实例 public void UnregisterHandler(IUiTransitionHandler handler) { _pipeline.UnregisterHandler(handler); } /// /// 绑定UI根节点 /// /// UI根节点实例 public void BindRoot(IUiRoot root) { _uiRoot = root; Log.Debug("Bind UI Root: {0}", root.GetType().Name); } #region Page Stack Management /// /// 将指定的UI界面压入路由栈 /// /// UI页面的唯一标识键 /// 页面进入参数 /// UI过渡策略 public async ValueTask PushAsync(string uiKey, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive) { 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}, stackBefore={2}", uiKey, policy, _stack.Count); await _pipeline.ExecuteAroundAsync(@event, async () => { await BeforeChangeAsync(@event); await DoPushPageInternalAsync(uiKey, param, policy); await AfterChangeAsync(@event); }); } /// /// 将已存在的UI页面压入栈顶 /// /// 已存在的UI页面行为实例 /// 页面进入参数 /// UI过渡策略 public async ValueTask PushAsync(IUiPageBehavior page, IUiPageEnterParam? param = null, UiTransitionPolicy policy = UiTransitionPolicy.Exclusive) { var uiKey = page.Key; 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); await _pipeline.ExecuteAroundAsync(@event, async () => { await BeforeChangeAsync(@event); DoPushPageInternal(page, param, policy); await AfterChangeAsync(@event); }); } /// /// 弹出栈顶页面 /// /// 页面弹出策略 public async ValueTask PopAsync(UiPopPolicy policy = UiPopPolicy.Destroy) { if (_stack.Count == 0) { Log.Debug("Pop ignored: stack is empty"); return; } var leavingUiKey = _stack.Peek().Key; if (!await ExecuteLeaveGuardsAsync(leavingUiKey)) { Log.Warn("Pop blocked by guard: {0}", leavingUiKey); return; } var nextUiKey = _stack.Count > 1 ? _stack.ElementAt(1).Key : null; var @event = CreateEvent(nextUiKey, UiTransitionType.Pop); await _pipeline.ExecuteAroundAsync(@event, async () => { await BeforeChangeAsync(@event); DoPopInternal(policy); await AfterChangeAsync(@event); }); } /// /// 替换当前所有页面为新页面(基于uiKey) /// /// 新UI页面的唯一标识键 /// 页面进入参数 /// 页面弹出策略 /// 页面压入策略 public async ValueTask ReplaceAsync(string uiKey, IUiPageEnterParam? param = null, UiPopPolicy popPolicy = UiPopPolicy.Destroy, UiTransitionPolicy pushPolicy = UiTransitionPolicy.Exclusive) { var @event = CreateEvent(uiKey, UiTransitionType.Replace, pushPolicy, param); Log.Debug("Replace UI Stack with page: key={0}, popPolicy={1}, pushPolicy={2}", uiKey, popPolicy, pushPolicy); await _pipeline.ExecuteAroundAsync(@event, async () => { await BeforeChangeAsync(@event); DoClearInternal(popPolicy); var page = _factory.Create(uiKey); Log.Debug("Get/Create UI Page instance for Replace: {0}", page.GetType().Name); DoPushPageInternal(page, param, pushPolicy); await AfterChangeAsync(@event); }); } /// /// 替换当前所有页面为已存在的页面 /// /// 已存在的UI页面行为实例 /// 页面进入参数 /// 页面弹出策略 /// 页面压入策略 public async ValueTask ReplaceAsync(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); await _pipeline.ExecuteAroundAsync(@event, async () => { await BeforeChangeAsync(@event); DoClearInternal(popPolicy); Log.Debug("Use existing UI Page instance for Replace: {0}", page.GetType().Name); DoPushPageInternal(page, param, pushPolicy); await AfterChangeAsync(@event); }); } /// /// 清空所有页面栈 /// public async ValueTask ClearAsync() { var @event = CreateEvent(string.Empty, UiTransitionType.Clear); Log.Debug("Clear UI Stack, stackCount={0}", _stack.Count); await _pipeline.ExecuteAroundAsync(@event, async () => { await BeforeChangeAsync(@event); DoClearInternal(UiPopPolicy.Destroy); await AfterChangeAsync(@event); }); } /// /// 获取栈顶元素的键值 /// /// 栈顶UI页面的键值,如果栈为空则返回空字符串 public string PeekKey() { return _stack.Count == 0 ? string.Empty : _stack.Peek().Key; } /// /// 获取栈顶元素 /// /// 栈顶UI页面行为实例,如果栈为空则返回null public IUiPageBehavior? Peek() { return _stack.Count == 0 ? null : _stack.Peek(); } /// /// 判断栈顶是否为指定UI /// /// 要检查的UI页面键值 /// 如果栈顶是指定UI则返回true,否则返回false 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; #endregion #region Layer UI Management /// /// 在指定层级显示UI(基于 uiKey) /// /// UI页面的唯一标识键 /// UI显示层级 /// 页面进入参数 /// UI句柄实例 /// 当尝试在Page层级使用此方法时抛出 public UiHandle Show(string uiKey, UiLayer layer, IUiPageEnterParam? param = null) { if (layer == UiLayer.Page) throw new ArgumentException("Use Push() for Page layer"); // 创建实例 var page = _factory.Create(uiKey); return ShowInternal(page, layer, param); } /// /// 在指定层级显示UI(基于实例) /// /// UI页面行为实例 /// UI显示层级 /// UI句柄实例 /// 当尝试在Page层级使用此方法时抛出 public UiHandle Show(IUiPageBehavior page, UiLayer layer) { if (layer == UiLayer.Page) throw new ArgumentException("Use Push() for Page layer"); return ShowInternal(page, layer, null); } /// /// 隐藏指定层级的UI /// /// UI句柄 /// UI层级 /// 是否销毁UI实例,默认为false public void Hide(UiHandle handle, UiLayer layer, bool destroy = false) { if (!_layers.TryGetValue(layer, out var layerDict)) return; if (!layerDict.TryGetValue(handle.InstanceId, out var page)) return; if (destroy) { page.OnExit(); _uiRoot.RemoveUiPage(page); layerDict.Remove(handle.InstanceId); Log.Debug("Hide & Destroy UI: instanceId={0}, layer={1}", handle.InstanceId, layer); } else { page.OnHide(); Log.Debug("Hide UI (suspend): instanceId={0}, layer={1}", handle.InstanceId, layer); } } /// /// 恢复指定UI的显示 /// /// UI句柄 /// UI层级 public void Resume(UiHandle handle, UiLayer layer) { if (!_layers.TryGetValue(layer, out var layerDict)) return; if (!layerDict.TryGetValue(handle.InstanceId, out var page)) return; page.OnShow(); page.OnResume(); Log.Debug("Resume UI: instanceId={0}, layer={1}", handle.InstanceId, layer); } /// /// 清空指定层级的所有UI /// /// 要清空的UI层级 /// 是否销毁UI实例,默认为false public void ClearLayer(UiLayer layer, bool destroy = false) { if (!_layers.TryGetValue(layer, out var layerDict)) return; var handles = layerDict.Keys .Select(instanceId => { var page = layerDict[instanceId]; return new UiHandle(page.Key, instanceId, layer); }) .ToArray(); foreach (var handle in handles) Hide(handle, layer, destroy); Log.Debug("Cleared layer: {0}, destroyed={1}", layer, destroy); } /// /// 获取指定层级的UI实例 /// /// UI句柄 /// UI层级 /// 如果找到则返回UI句柄,否则返回null public UiHandle? GetFromLayer(UiHandle handle, UiLayer layer) { if (!_layers.TryGetValue(layer, out var layerDict)) return null; return layerDict.ContainsKey(handle.InstanceId) ? handle : null; } /// /// 获取指定 uiKey 在指定层级的所有实例 /// /// UI页面的唯一标识键 /// UI层级 /// 指定UI在该层级的所有实例句柄列表 public IReadOnlyList GetAllFromLayer(string uiKey, UiLayer layer) { if (!_layers.TryGetValue(layer, out var layerDict)) return Array.Empty(); return layerDict .Where(kvp => kvp.Value.Key.Equals(uiKey)) .Select(kvp => new UiHandle(uiKey, kvp.Key, layer)) .ToList(); } /// /// 判断指定UI是否在层级中可见 /// /// UI句柄 /// UI层级 /// 如果UI在层级中且可见则返回true,否则返回false public bool HasVisibleInLayer(UiHandle handle, UiLayer layer) { if (!_layers.TryGetValue(layer, out var layerDict)) return false; if (!layerDict.TryGetValue(handle.InstanceId, out var page)) return false; return page.IsVisible; } /// /// 根据UI键隐藏指定层级中的UI。 /// /// UI的唯一标识键。 /// 要操作的UI层级。 /// 是否销毁UI实例,默认为false。 /// 是否隐藏所有匹配的UI实例,默认为false。 public void HideByKey(string uiKey, UiLayer layer, bool destroy = false, bool hideAll = false) { var handles = GetAllFromLayer(uiKey, layer); if (handles.Count == 0) return; if (hideAll) foreach (var h in handles) { Hide(h, layer, destroy); } else Hide(handles[0], layer, destroy); } #endregion #region Route Guards /// /// 注册路由守卫 /// /// 路由守卫实例 /// 当守卫实例为null时抛出 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); } /// /// 注册路由守卫(泛型) /// /// 路由守卫类型,必须实现IUiRouteGuard接口且有无参构造函数 public void AddGuard() where T : IUiRouteGuard, new() { AddGuard(new T()); } /// /// 移除路由守卫 /// /// 要移除的路由守卫实例 /// 当守卫实例为null时抛出 public void RemoveGuard(IUiRouteGuard guard) { ArgumentNullException.ThrowIfNull(guard); if (_guards.Remove(guard)) Log.Debug("Guard removed: {0}", guard.GetType().Name); } #endregion #region Initialization /// /// 初始化函数,在对象创建时调用。 /// 该函数负责获取UI工厂实例并注册处理程序。 /// protected override void OnInit() { // 获取UI工厂实例,并确保其不为null _factory = this.GetUtility()!; // 输出调试日志,记录UI路由器基类已初始化及使用的工厂类型 Log.Debug("UiRouterBase initialized. Factory={0}", _factory.GetType().Name); // 调用抽象方法以注册具体的处理程序 RegisterHandlers(); } /// /// 抽象方法,用于注册具体的处理程序。 /// 子类必须实现此方法以完成特定的处理逻辑注册。 /// protected abstract void RegisterHandlers(); #endregion #region Internal Helpers /// /// 生成唯一实例ID /// /// 格式为"ui_000001"的唯一实例标识符 private string GenerateInstanceId() { // 原子操作递增实例计数器,确保多线程环境下的唯一性 var id = Interlocked.Increment(ref _instanceCounter); // 返回格式化的实例ID字符串 return $"ui_{id:D6}"; } /// /// 内部Show实现,支持重入 /// /// UI页面行为实例 /// UI显示层级 /// 页面进入参数 /// UI句柄实例 /// 当UI不支持重入且已在该层级存在时抛出 private UiHandle ShowInternal(IUiPageBehavior page, UiLayer layer, IUiPageEnterParam? param) { var instanceId = GenerateInstanceId(); var handle = new UiHandle(page.Key, instanceId, layer); // 初始化层级字典 if (!_layers.ContainsKey(layer)) _layers[layer] = new Dictionary(); // 设置句柄 page.Handle = handle; var layerDict = _layers[layer]; // 检查重入性 if (!page.IsReentrant && layerDict.Values.Any(p => p.Key == page.Key)) { Log.Warn("UI {0} is not reentrant but already exists in layer {1}", page.Key, layer); throw new InvalidOperationException( $"UI {page.Key} does not support multiple instances in layer {layer}"); } // 添加到层级管理 layerDict[instanceId] = page; // 添加到UiRoot _uiRoot.AddUiPage(page, layer); // 生命周期 page.OnEnter(param); page.OnShow(); Log.Debug("Show UI: key={0}, instanceId={1}, layer={2}", page.Key, instanceId, layer); return handle; } /// /// 创建UI过渡事件 /// /// 目标UI键值 /// 过渡类型 /// 过渡策略 /// 进入参数 /// 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过渡事件 private async Task BeforeChangeAsync(UiTransitionEvent @event) { Log.Debug("BeforeChange phases started: {0}", @event.TransitionType); await _pipeline.ExecuteAsync(@event, UiTransitionPhases.BeforeChange); Log.Debug("BeforeChange phases completed: {0}", @event.TransitionType); } /// /// 执行过渡后阶段 /// /// UI过渡事件 private async Task AfterChangeAsync(UiTransitionEvent @event) { Log.Debug("AfterChange phases started: {0}", @event.TransitionType); await _pipeline.ExecuteAsync(@event, UiTransitionPhases.AfterChange); Log.Debug("AfterChange phases completed: {0}", @event.TransitionType); } /// /// 内部异步压入页面实现 /// /// UI页面键值 /// 页面进入参数 /// 过渡策略 private async Task DoPushPageInternalAsync(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy) { if (!await ExecuteEnterGuardsAsync(uiKey, param)) { Log.Warn("Push blocked by guard: {0}", uiKey); return; } var page = _factory.Create(uiKey); Log.Debug("Get/Create UI Page instance: {0}", page.GetType().Name); DoPushPageInternal(page, param, policy); } /// /// 内部压入页面实现 /// /// UI页面行为实例 /// 页面进入参数 /// 过渡策略 private void DoPushPageInternal(IUiPageBehavior page, IUiPageEnterParam? param, UiTransitionPolicy policy) { 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("Suspend current page (Exclusive): {0}", current.View.GetType().Name); current.OnHide(); } } Log.Debug("Add page to UiRoot: {0}", page.View.GetType().Name); _uiRoot.AddUiPage(page); _stack.Push(page); Log.Debug("Enter & Show page: {0}, stackAfter={1}", page.View.GetType().Name, _stack.Count); page.OnEnter(param); page.OnShow(); } /// /// 内部弹出页面实现 /// /// 页面弹出策略 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); if (policy == UiPopPolicy.Destroy) { top.OnExit(); _uiRoot.RemoveUiPage(top); } else { top.OnHide(); } if (_stack.Count > 0) { var next = _stack.Peek(); next.OnResume(); next.OnShow(); } } /// /// 内部清空页面实现 /// /// 页面弹出策略 private void DoClearInternal(UiPopPolicy policy) { Log.Debug("Clear UI Stack internal, count={0}", _stack.Count); while (_stack.Count > 0) DoPopInternal(policy); } /// /// 执行进入守卫检查 /// /// UI页面键值 /// 页面进入参数 /// 如果允许进入则返回true,否则返回false 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; } /// /// 执行离开守卫检查 /// /// UI页面键值 /// 如果允许离开则返回true,否则返回false 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 }