feat(upserver): 接入认证与KV存档并完成限定钓鱼闭环
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace game.PlayerDataStorage
|
||||
{
|
||||
internal enum PlayerKVShutdownStatus { Ready, Pending, Failed, Cancelled, Input }
|
||||
|
||||
/// <summary>Process-lifetime barrier. Faulted and unfinished work cannot be reset by replacing a scene/container.</summary>
|
||||
internal sealed class PlayerKVShutdownRegistry
|
||||
{
|
||||
internal static PlayerKVShutdownRegistry Process { get; } = new PlayerKVShutdownRegistry();
|
||||
readonly object gate = new object();
|
||||
readonly Dictionary<Task, PlayerKVLoginContext> pending = new Dictionary<Task, PlayerKVLoginContext>();
|
||||
TaskCompletionSource<bool> changed = Signal();
|
||||
|
||||
internal void Register(Task task, PlayerKVLoginContext owner = null)
|
||||
{
|
||||
if (task == null) throw new ArgumentNullException(nameof(task));
|
||||
lock (gate)
|
||||
{
|
||||
if (!pending.ContainsKey(task) || owner != null) pending[task] = owner;
|
||||
NotifyChanged();
|
||||
}
|
||||
_ = task.ContinueWith(t =>
|
||||
{
|
||||
if (t.IsFaulted) { _ = t.Exception; }
|
||||
lock (gate)
|
||||
{
|
||||
if (t.Status == TaskStatus.RanToCompletion) pending.Remove(t);
|
||||
NotifyChanged();
|
||||
}
|
||||
// Cancelled work does not prove resource cleanup; retain it as a failure barrier.
|
||||
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
|
||||
}
|
||||
|
||||
internal PlayerKVShutdownStatus Status
|
||||
{
|
||||
get
|
||||
{
|
||||
lock (gate)
|
||||
{
|
||||
if (pending.Keys.Any(t => t.IsFaulted || t.IsCanceled)) return PlayerKVShutdownStatus.Failed;
|
||||
return pending.Keys.Any(t => !t.IsCompleted) ? PlayerKVShutdownStatus.Pending : PlayerKVShutdownStatus.Ready;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Injection is an observation clock only, never a force-close or permission to reopen.
|
||||
internal async Task<PlayerKVShutdownStatus> ObserveAsync(int timeoutMs, CancellationToken token = default,
|
||||
Func<int, CancellationToken, Task> delay = null)
|
||||
{
|
||||
if (timeoutMs < 1000 || timeoutMs > 15000) return PlayerKVShutdownStatus.Input;
|
||||
if (token.IsCancellationRequested) return PlayerKVShutdownStatus.Cancelled;
|
||||
using (var clock = CancellationTokenSource.CreateLinkedTokenSource(token))
|
||||
{
|
||||
var deadline = (delay ?? Task.Delay)(timeoutMs, clock.Token);
|
||||
try
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
if (token.IsCancellationRequested) return PlayerKVShutdownStatus.Cancelled;
|
||||
Task notification;
|
||||
lock (gate)
|
||||
{
|
||||
var status = Status;
|
||||
if (status != PlayerKVShutdownStatus.Pending) return status;
|
||||
notification = changed.Task;
|
||||
}
|
||||
if (await Task.WhenAny(notification, deadline).ConfigureAwait(false) == deadline)
|
||||
{
|
||||
if (token.IsCancellationRequested) return PlayerKVShutdownStatus.Cancelled;
|
||||
return Status;
|
||||
}
|
||||
// Recheck the live collection; another Root may have registered work during the await.
|
||||
}
|
||||
}
|
||||
finally { clock.Cancel(); }
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyChanged()
|
||||
{
|
||||
var previous = changed; changed = Signal(); previous.TrySetResult(true);
|
||||
}
|
||||
|
||||
static TaskCompletionSource<bool> Signal() =>
|
||||
new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user