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