using GFramework.Core.Abstractions.Pause;
using GFramework.Core.Extensions;
using GFramework.Game.Abstractions.Enums;
using GFramework.Game.Abstractions.UI;
using GFramework.Game.Routing;
namespace GFramework.Game.UI;
///
/// UI路由基类,提供页面栈管理和层级UI管理功能
/// 负责UI页面的导航、显示、隐藏以及生命周期管理
///
public abstract class UiRouterBase : RouterBase, IUiRouter
{
private static readonly ILogger Log = LoggerFactoryResolver.Provider.CreateLogger(nameof(UiRouterBase));
///
/// 层级管理字典(非栈层级),用于管理Overlay、Modal、Toast等浮层UI
/// Key: UiLayer枚举值, Value: InstanceId到PageBehavior的映射字典
///
private readonly Dictionary> _layers = new();
///
/// 记录当前由页面可见性驱动持有的暂停令牌。
///
private readonly Dictionary _pauseTokens = new();
///
/// UI切换处理器管道,用于执行UI过渡动画和逻辑
///
private readonly UiTransitionPipeline _pipeline = new();
///
/// UI工厂实例,用于创建UI页面和相关对象
///
private IUiFactory _factory = null!;
///
/// 实例ID计数器,用于生成唯一的UI实例标识符
///
private int _instanceCounter;
///
/// 可选暂停栈管理器。
///
private IPauseStackManager? _pauseStackManager;
///
/// 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).ConfigureAwait(true);
await DoPushPageInternalAsync(uiKey, param, policy).ConfigureAwait(true);
await AfterChangeAsync(@event).ConfigureAwait(true);
}).ConfigureAwait(true);
}
///
/// 将已存在的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).ConfigureAwait(true);
DoPushPageInternal(page, param, policy);
await AfterChangeAsync(@event).ConfigureAwait(true);
}).ConfigureAwait(true);
}
///
/// 弹出栈顶页面
///
/// 页面弹出策略
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).ConfigureAwait(true))
{
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).ConfigureAwait(true);
DoPopInternal(policy);
await AfterChangeAsync(@event).ConfigureAwait(true);
}).ConfigureAwait(true);
}
///
/// 替换当前所有页面为新页面(基于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).ConfigureAwait(true);
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).ConfigureAwait(true);
}).ConfigureAwait(true);
}
///
/// 替换当前所有页面为已存在的页面
///
/// 已存在的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).ConfigureAwait(true);
DoClearInternal(popPolicy);
Log.Debug("Use existing UI Page instance for Replace: {0}", page.GetType().Name);
DoPushPageInternal(page, param, pushPolicy);
await AfterChangeAsync(@event).ConfigureAwait(true);
}).ConfigureAwait(true);
}
///
/// 清空所有页面栈
///
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).ConfigureAwait(true);
DoClearInternal(UiPopPolicy.Destroy);
await AfterChangeAsync(@event).ConfigureAwait(true);
}).ConfigureAwait(true);
}
///
/// 获取栈顶元素的键值
///
/// 栈顶UI页面的键值,如果栈为空则返回空字符串
public new 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 new bool IsTop(string uiKey)
{
return Stack.Count != 0 && string.Equals(Stack.Peek().Key, uiKey, StringComparison.Ordinal);
}
///
/// 判断栈中是否包含指定UI
///
/// 要检查的UI页面键值
/// 如果栈中包含指定UI则返回true,否则返回false
public new bool Contains(string uiKey)
{
return Stack.Any(p => string.Equals(p.Key, uiKey, StringComparison.Ordinal));
}
///
/// 获取栈深度
///
public new 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", nameof(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", nameof(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();
SyncPauseRequest(page, isVisible: false);
_uiRoot.RemoveUiPage(page);
layerDict.Remove(handle.InstanceId);
Log.Debug("Hide & Destroy UI: instanceId={0}, layer={1}", handle.InstanceId, layer);
}
else
{
page.OnHide();
SyncPauseRequest(page, isVisible: false);
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();
SyncPauseRequest(page, isVisible: true);
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 => string.Equals(kvp.Value.Key, uiKey, StringComparison.Ordinal))
.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);
}
///
/// 获取当前拥有指定 UI 语义动作捕获权的页面。
///
/// 要查询的动作。
/// 动作所有者;若没有页面声明捕获该动作则返回 。
public IUiPageBehavior? GetUiActionOwner(UiInputAction action)
{
return EnumerateVisiblePagesByPriority()
.FirstOrDefault(page => UiInteractionProfiles.Captures(page.InteractionProfile, action));
}
///
/// 尝试将语义动作分发给当前拥有捕获权的页面。
///
/// 当前动作。
/// 如果已有页面捕获该动作则返回 。
public bool TryDispatchUiAction(UiInputAction action)
{
var owner = GetUiActionOwner(action);
if (owner is null)
return false;
var handled = owner.TryHandleUiAction(action);
if (!handled)
Log.Debug("UI action captured without explicit handler: key={0}, action={1}", owner.Key, action);
return true;
}
///
/// 尝试将语义动作分发给当前拥有捕获权的页面。
///
/// 当前动作。
/// 如果已有页面捕获该动作则返回 。
[Obsolete(
"Use TryDispatchUiAction(UiInputAction action) to emphasize dispatch semantics instead of handler success.")]
public bool TryHandleUiAction(UiInputAction action)
{
return TryDispatchUiAction(action);
}
///
/// 判断当前可见 UI 是否阻断 World 指针输入。
///
/// 如果 World 指针输入应被阻断则返回 。
public bool BlocksWorldPointerInput()
{
return EnumerateVisiblePagesByPriority()
.Any(page => page.InteractionProfile.BlocksWorldPointerInput);
}
///
/// 判断当前可见 UI 是否阻断 World 语义动作输入。
///
/// 如果 World 语义动作输入应被阻断则返回 。
public bool BlocksWorldActionInput()
{
return EnumerateVisiblePagesByPriority()
.Any(page => page.InteractionProfile.BlocksWorldActionInput);
}
#endregion
#region Initialization
///
/// 初始化函数,在对象创建时调用。
/// 该函数负责获取UI工厂实例并注册处理程序。
///
protected override void OnInit()
{
// 获取UI工厂实例,并确保其不为null
_factory = this.GetUtility()!;
TryBindPauseStackManager();
// 输出调试日志,记录UI路由器基类已初始化及使用的工厂类型
Log.Debug("UiRouterBase initialized. Factory={0}", _factory.GetType().Name);
// 调用抽象方法以注册具体的处理程序
RegisterHandlers();
}
///
/// 抽象方法,用于注册具体的处理程序。
/// 子类必须实现此方法以完成特定的处理逻辑注册。
///
protected override abstract void RegisterHandlers();
///
/// 路由销毁时释放所有由页面持有的暂停请求。
///
protected override void OnDestroy()
{
base.OnDestroy();
if (_pauseStackManager is null)
return;
foreach (var token in _pauseTokens.Values.ToArray())
{
_pauseStackManager.Pop(token);
}
_pauseTokens.Clear();
}
#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.TryGetValue(layer, out var layerDict))
{
layerDict = new Dictionary(StringComparer.Ordinal);
_layers[layer] = layerDict;
}
// 设置句柄
page.Handle = handle;
// 检查重入性
if (!page.IsReentrant &&
layerDict.Values.Any(p => string.Equals(p.Key, page.Key, StringComparison.Ordinal)))
{
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();
SyncPauseRequest(page, isVisible: true);
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).ConfigureAwait(true);
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).ConfigureAwait(true);
Log.Debug("AfterChange phases completed: {0}", @event.TransitionType);
}
///
/// 内部异步压入页面实现
///
/// UI页面键值
/// 页面进入参数
/// 过渡策略
private async Task DoPushPageInternalAsync(string uiKey, IUiPageEnterParam? param, UiTransitionPolicy policy)
{
if (!await ExecuteEnterGuardsAsync(uiKey, param).ConfigureAwait(true))
{
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();
SyncPauseRequest(current, isVisible: false);
}
}
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();
SyncPauseRequest(page, isVisible: true);
}
///
/// 内部弹出页面实现
///
/// 页面弹出策略
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();
}
SyncPauseRequest(top, isVisible: false);
if (Stack.Count > 0)
{
var next = Stack.Peek();
next.OnShow();
SyncPauseRequest(next, isVisible: true);
}
}
///
/// 内部清空页面实现
///
/// 页面弹出策略
private void DoClearInternal(UiPopPolicy policy)
{
Log.Debug("Clear UI Stack internal, count={0}", Stack.Count);
while (Stack.Count > 0)
DoPopInternal(policy);
}
///
/// 尝试绑定暂停栈管理器。
///
private void TryBindPauseStackManager()
{
try
{
_pauseStackManager = this.GetUtility();
}
catch (InvalidOperationException)
{
_pauseStackManager = null;
Log.Debug("PauseStackManager not available. Pause integration is disabled for the UI router.");
}
}
///
/// 根据页面可见性同步暂停请求。
///
/// 页面行为。
/// 页面是否应视为可见。
private void SyncPauseRequest(IUiPageBehavior page, bool isVisible)
{
if (_pauseStackManager is null)
return;
var profile = page.InteractionProfile;
if (!isVisible || profile.PauseMode == UiPauseMode.None)
{
ReleasePauseRequest(page);
return;
}
if (_pauseTokens.ContainsKey(page))
return;
var reason = string.IsNullOrWhiteSpace(profile.PauseReason)
? $"UI:{page.Key}"
: profile.PauseReason;
_pauseTokens[page] = _pauseStackManager.Push(reason, profile.PauseGroup);
}
///
/// 释放页面此前登记的暂停请求。
///
/// 目标页面。
private void ReleasePauseRequest(IUiPageBehavior page)
{
if (_pauseStackManager is null)
return;
if (!_pauseTokens.Remove(page, out var token))
return;
_pauseStackManager.Pop(token);
}
///
/// 按输入优先级枚举当前所有可见页面。
///
/// 可见页面序列。
private IEnumerable EnumerateVisiblePagesByPriority()
{
foreach (var page in EnumerateVisibleLayerPages(UiLayer.Topmost))
yield return page;
foreach (var page in EnumerateVisibleLayerPages(UiLayer.Modal))
yield return page;
foreach (var page in EnumerateVisibleLayerPages(UiLayer.Overlay))
yield return page;
foreach (var page in Stack.Where(static page => page.IsAlive && page.IsVisible))
yield return page;
foreach (var page in EnumerateVisibleLayerPages(UiLayer.Toast))
yield return page;
}
///
/// 枚举指定层级中的可见页面,层内按最近显示优先。
///
/// 目标层级。
/// 该层级中的可见页面。
private IEnumerable EnumerateVisibleLayerPages(UiLayer layer)
{
if (!_layers.TryGetValue(layer, out var layerDict))
yield break;
foreach (var page in layerDict
// Use the numeric sequence encoded in the instance id so ordering stays correct after width overflow.
.OrderByDescending(static pair => ExtractInstanceSequence(pair.Key))
.Select(static pair => pair.Value)
.Where(static page => page.IsAlive && page.IsVisible))
{
yield return page;
}
}
///
/// 从实例标识符中提取自增序号,供层内最近显示优先排序使用。
///
/// 实例标识符,预期格式为 ui_000001。
/// 提取到的自增序号;若格式异常则返回 ,使异常值排在最后。
private static int ExtractInstanceSequence(string instanceId)
{
return instanceId.Length > 3 &&
int.TryParse(instanceId.AsSpan(3), NumberStyles.None, CultureInfo.InvariantCulture, out var sequence)
? sequence
: int.MinValue;
}
#endregion
}