using System.Threading; using System.Threading.Tasks; namespace BaseGames.UI { /// /// 返回结果的模态面板基类(确认框、模式 / 难度选择等)。 /// /// 用法(调用方走线性 await,无回调嵌套): /// /// bool ok = await _confirmDialog.ShowAsync("CONFIRM_OVERWRITE_TITLE", "CONFIRM_OVERWRITE_BODY", ct); /// if (!ok) return; /// /// /// 结果通道由 承载,并对所有"提前结束"路径兜底 /// (ESC 取消出栈、外部强关、所属场景卸载、CancellationToken 取消),保证 await 绝不悬挂。 /// public abstract class UIResultPanel : UIPanelBase { private TaskCompletionSource _tcs; private CancellationTokenRegistration _ctReg; /// 取消 / 默认结果:ESC、返回、销毁、场景卸载、ct 取消时以此完成。 protected abstract T CancelResult { get; } /// 由导航器 调用:开启一轮结果等待。 internal Task BeginResult(CancellationToken ct) { // 复用面板:若上一轮仍未决,先以默认值收口,避免句柄泄漏。 ResolvePending(); _tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); _ctReg = ct.CanBeCanceled ? ct.Register(ResolvePending) : default; return _tcs.Task; } /// 具体按钮回调:以 完成并出栈自身。 protected void Complete(T result) { var tcs = _tcs; if (tcs != null && tcs.TrySetResult(result)) { _ctReg.Dispose(); GetService()?.Pop(); // 弹出自己(栈顶) } } /// 以取消默认值收口未决结果(幂等)。 private void ResolvePending() { var tcs = _tcs; if (tcs != null && tcs.TrySetResult(CancelResult)) _ctReg.Dispose(); } // 出栈(含 ESC 取消)会 SetActive(false) → OnDisable → OnPanelClose; // 场景卸载 / 销毁同样经 OnDisable。统一在此兜底,覆盖所有提前结束路径。 protected override void OnPanelClose() => ResolvePending(); } }