// Copyright (c) 2025-2026 GeWuYou // SPDX-License-Identifier: Apache-2.0 using GFramework.Core.Abstractions.Lifecycle; using GFramework.Core.Abstractions.Logging; using GFramework.Core.Abstractions.Pause; using GFramework.Core.Logging; using GFramework.Core.Utility; namespace GFramework.Core.Pause; /// /// 暂停栈管理器实现,用于管理游戏中的暂停状态。 /// 支持多组暂停、嵌套暂停、以及暂停状态的通知机制。 /// public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAsyncDestroyable { private readonly List _handlers = new(); private readonly ReaderWriterLockSlim _lock = new(); private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(PauseStackManager)); private readonly Dictionary> _pauseStacks = new(); private readonly Dictionary _tokenMap = new(); private volatile bool _disposed; /// /// 异步销毁方法,在组件销毁时调用。 /// /// 表示异步操作完成的任务。 public ValueTask DestroyAsync() { if (_disposed) { return ValueTask.CompletedTask; } var destroySnapshot = TryBeginDestroy(); if (destroySnapshot == null) { return ValueTask.CompletedTask; } NotifyDestroyedGroups(destroySnapshot.Value); _lock.Dispose(); return ValueTask.CompletedTask; } /// /// 暂停状态变化事件,当暂停状态发生改变时触发。 /// public event EventHandler? OnPauseStateChanged; /// /// 推入一个新的暂停请求到指定的暂停组中。 /// /// 暂停的原因描述。 /// 暂停组,默认为全局暂停组。 /// 表示此次暂停请求的令牌。 public PauseToken Push(string reason, PauseGroup group = PauseGroup.Global) { PauseToken token; bool shouldNotify = false; _lock.EnterWriteLock(); try { ThrowIfDisposed(); var wasPaused = IsPausedInternal(group); var entry = new PauseEntry { TokenId = Guid.NewGuid(), Reason = reason, Group = group, Timestamp = DateTime.UtcNow }; if (!_pauseStacks.TryGetValue(group, out var stack)) { stack = new Stack(); _pauseStacks[group] = stack; } stack.Push(entry); _tokenMap[entry.TokenId] = entry; _logger.Debug($"Pause pushed: {reason} (Group: {group}, Depth: {stack.Count})"); token = new PauseToken(entry.TokenId); // 状态变化检测:从未暂停 → 暂停 shouldNotify = !wasPaused; } finally { _lock.ExitWriteLock(); } // 在锁外通知处理器,避免死锁 if (shouldNotify) { NotifyHandlers(group, true); } return token; } /// /// 弹出指定的暂停请求。 /// /// 要弹出的暂停令牌。 /// 如果成功弹出则返回true,否则返回false。 public bool Pop(PauseToken token) { if (!token.IsValid) { return false; } var result = TryPopEntry(token); if (result.ShouldNotify) { NotifyHandlers(result.NotifyGroup, false); } return result.Found; } /// /// 查询指定暂停组当前是否处于暂停状态。 /// /// 要查询的暂停组,默认为全局暂停组。 /// 如果该组处于暂停状态则返回true,否则返回false。 public bool IsPaused(PauseGroup group = PauseGroup.Global) { _lock.EnterReadLock(); try { ThrowIfDisposed(); return IsPausedInternal(group); } finally { _lock.ExitReadLock(); } } /// /// 获取指定暂停组的暂停深度(即嵌套暂停的层数)。 /// /// 要查询的暂停组,默认为全局暂停组。 /// 暂停深度,0表示未暂停。 public int GetPauseDepth(PauseGroup group = PauseGroup.Global) { _lock.EnterReadLock(); try { ThrowIfDisposed(); return _pauseStacks.TryGetValue(group, out var stack) ? stack.Count : 0; } finally { _lock.ExitReadLock(); } } /// /// 获取指定暂停组的所有暂停原因。 /// /// 要查询的暂停组,默认为全局暂停组。 /// 包含所有暂停原因的只读列表。 public IReadOnlyList GetPauseReasons(PauseGroup group = PauseGroup.Global) { _lock.EnterReadLock(); try { ThrowIfDisposed(); if (!_pauseStacks.TryGetValue(group, out var stack)) return Array.Empty(); return stack.Select(e => e.Reason).ToList(); } finally { _lock.ExitReadLock(); } } /// /// 创建一个暂停作用域,支持 using 语法自动管理暂停生命周期。 /// /// 暂停的原因描述。 /// 暂停组,默认为全局暂停组。 /// 表示暂停作用域的 IDisposable 对象。 public IDisposable PauseScope(string reason, PauseGroup group = PauseGroup.Global) { if (_disposed) throw new ObjectDisposedException(nameof(PauseStackManager), "Cannot use PauseStackManager after it has been destroyed"); return new PauseScope(this, reason, group); } /// /// 清空指定暂停组的所有暂停请求。 /// /// 要清空的暂停组。 public void ClearGroup(PauseGroup group) { bool shouldNotify = false; _lock.EnterWriteLock(); try { ThrowIfDisposed(); if (!_pauseStacks.TryGetValue(group, out var stack)) return; var wasPaused = stack.Count > 0; // 移除所有令牌 foreach (var entry in stack) { _tokenMap.Remove(entry.TokenId); } stack.Clear(); _logger.Warn($"Cleared all pauses for group: {group}"); // 状态变化检测 shouldNotify = wasPaused; } finally { _lock.ExitWriteLock(); } // 在锁外通知处理器,避免死锁 if (shouldNotify) { NotifyHandlers(group, false); } } /// /// 清空所有暂停组的所有暂停请求。 /// public void ClearAll() { List pausedGroups; _lock.EnterWriteLock(); try { ThrowIfDisposed(); pausedGroups = _pauseStacks .Where(kvp => kvp.Value.Count > 0) .Select(kvp => kvp.Key) .ToList(); _pauseStacks.Clear(); _tokenMap.Clear(); _logger.Warn("Cleared all pauses for all groups"); } finally { _lock.ExitWriteLock(); } // 在锁外通知所有之前暂停的组,避免死锁 foreach (var group in pausedGroups) { NotifyHandlers(group, false); } } /// /// 注册一个暂停处理器,用于监听暂停状态的变化。 /// /// 要注册的暂停处理器。 public void RegisterHandler(IPauseHandler handler) { _lock.EnterWriteLock(); try { ThrowIfDisposed(); if (!_handlers.Contains(handler)) { _handlers.Add(handler); _logger.Debug($"Registered pause handler: {handler.GetType().Name}"); } } finally { _lock.ExitWriteLock(); } } /// /// 注销一个已注册的暂停处理器。 /// /// 要注销的暂停处理器。 public void UnregisterHandler(IPauseHandler handler) { _lock.EnterWriteLock(); try { ThrowIfDisposed(); if (_handlers.Remove(handler)) { _logger.Debug($"Unregistered pause handler: {handler.GetType().Name}"); } } finally { _lock.ExitWriteLock(); } } /// /// 检查是否已销毁,如果已销毁则抛出异常 /// private void ThrowIfDisposed() { if (_disposed) { throw new ObjectDisposedException(nameof(PauseStackManager), "Cannot use PauseStackManager after it has been destroyed"); } } /// /// 采集销毁所需的快照并清空内部状态。 /// /// /// 成功进入销毁阶段时返回销毁快照;如果其他线程已先完成销毁,则返回 。 /// /// /// 该方法只负责锁内状态迁移,把外部回调与事件派发留到锁外执行, /// 以避免在生命周期结束阶段持锁调用用户代码。 /// private DestroySnapshot? TryBeginDestroy() { _lock.EnterWriteLock(); try { if (_disposed) { return null; } _disposed = true; var pausedGroups = CollectPausedGroups(); var handlersSnapshot = CreateHandlerSnapshot(); _pauseStacks.Clear(); _tokenMap.Clear(); _handlers.Clear(); _logger.Debug("PauseStackManager destroyed"); return new DestroySnapshot(pausedGroups, handlersSnapshot); } finally { _lock.ExitWriteLock(); } } /// /// 在销毁后向所有先前处于暂停状态的分组补发恢复通知。 /// /// 销毁阶段采集的分组与处理器快照。 private void NotifyDestroyedGroups(DestroySnapshot destroySnapshot) { foreach (var group in destroySnapshot.PausedGroups) { _logger.Debug($"Notifying handlers of destruction: Group={group}, IsPaused=false"); NotifyHandlersSnapshot(group, false, destroySnapshot.HandlersSnapshot, isDestroying: true); RaiseDestroyStateChanged(group); } } /// /// 在锁内执行令牌移除,并返回锁外通知所需的信息。 /// /// 要移除的暂停令牌。 /// 包含本次弹出结果和后续通知决策的快照。 /// /// Pop 支持移除非栈顶令牌,因此这里会先临时转移栈元素,再恢复原有顺序, /// 只在最后一个暂停请求被移除时触发恢复通知。 /// private PopResult TryPopEntry(PauseToken token) { _lock.EnterWriteLock(); try { ThrowIfDisposed(); if (!_tokenMap.TryGetValue(token.Id, out var entry)) { _logger.Warn($"Attempted to pop invalid/expired token: {token.Id}"); return PopResult.NotFound; } var stack = _pauseStacks[entry.Group]; var wasPaused = stack.Count > 0; var found = RemoveEntryFromStack(stack, token.Id); if (!found) { return PopResult.NotFound; } _tokenMap.Remove(token.Id); _logger.Debug($"Pause popped: {entry.Reason} (Group: {entry.Group}, Remaining: {stack.Count})"); return new PopResult(true, wasPaused && stack.Count == 0, entry.Group); } finally { _lock.ExitWriteLock(); } } /// /// 从指定暂停栈中移除目标令牌,并保持其他暂停请求的原始顺序。 /// /// 要修改的暂停栈。 /// 目标令牌标识。 /// 如果找到了目标令牌则返回 private static bool RemoveEntryFromStack(Stack stack, Guid tokenId) { var tempStack = new Stack(); var found = false; while (stack.Count > 0) { var current = stack.Pop(); if (current.TokenId == tokenId) { found = true; break; } tempStack.Push(current); } while (tempStack.Count > 0) { stack.Push(tempStack.Pop()); } return found; } /// /// 收集当前仍处于暂停状态的分组列表。 /// /// 包含所有暂停中的分组的数组。 private PauseGroup[] CollectPausedGroups() { return _pauseStacks .Where(kvp => kvp.Value.Count > 0) .Select(kvp => kvp.Key) .ToArray(); } /// /// 按优先级创建处理器快照,确保锁外通知仍保持确定性顺序。 /// /// 已按优先级排序的处理器快照。 private IPauseHandler[] CreateHandlerSnapshot() { return _handlers .OrderBy(handler => handler.Priority) .ToArray(); } /// /// 统一使用给定的处理器快照派发暂停状态变化通知。 /// /// 发生状态变化的暂停组。 /// 新的暂停状态。 /// 要通知的处理器快照。 /// 是否处于销毁补发路径。 private void NotifyHandlersSnapshot( PauseGroup group, bool isPaused, IReadOnlyList handlersSnapshot, bool isDestroying) { foreach (var handler in handlersSnapshot) { try { handler.OnPauseStateChanged(group, isPaused); } catch (Exception ex) { var message = isDestroying ? $"Handler {handler.GetType().Name} failed during destruction" : $"Handler {handler.GetType().Name} failed"; _logger.Error(message, ex); } } } /// /// 在销毁路径中独立保护事件通知,避免订阅方异常中断其他分组的恢复信号。 /// /// 需要补发恢复事件的暂停组。 private void RaiseDestroyStateChanged(PauseGroup group) { try { RaisePauseStateChanged(group, false); } catch (Exception ex) { _logger.Error($"Event subscriber failed during destruction for group {group}", ex); } } /// /// 内部查询暂停状态的方法,不加锁。 /// /// 要查询的暂停组。 /// 如果该组处于暂停状态则返回true,否则返回false。 private bool IsPausedInternal(PauseGroup group) { return _pauseStacks.TryGetValue(group, out var stack) && stack.Count > 0; } /// /// 通知所有已注册的处理器和事件订阅者暂停状态的变化。 /// /// 发生状态变化的暂停组。 /// 新的暂停状态。 private void NotifyHandlers(PauseGroup group, bool isPaused) { _logger.Debug($"Notifying handlers: Group={group}, IsPaused={isPaused}"); // 在锁内获取处理器快照,避免并发修改异常 IPauseHandler[] handlersSnapshot; _lock.EnterReadLock(); try { handlersSnapshot = CreateHandlerSnapshot(); } finally { _lock.ExitReadLock(); } // 在锁外遍历快照并通知处理器 NotifyHandlersSnapshot(group, isPaused, handlersSnapshot, isDestroying: false); // 触发事件 RaisePauseStateChanged(group, isPaused); } /// /// 以标准事件模式发布暂停状态变化事件。 /// 所有状态变更路径都通过该方法创建统一的事件参数,避免不同调用点出现不一致的载荷。 /// /// 发生状态变化的暂停组。 /// 暂停组变化后的新状态。 private void RaisePauseStateChanged(PauseGroup group, bool isPaused) { OnPauseStateChanged?.Invoke(this, new PauseStateChangedEventArgs(group, isPaused)); } /// /// 初始化方法,在组件初始化时调用。 /// protected override void OnInit() { } /// /// 锁内采集的销毁快照,供锁外补发恢复通知使用。 /// /// 销毁前仍处于暂停状态的分组。 /// 按优先级排序后的处理器快照。 private readonly record struct DestroySnapshot(PauseGroup[] PausedGroups, IPauseHandler[] HandlersSnapshot); /// /// Pop 操作的锁内结果快照。 /// /// 是否成功移除了目标令牌。 /// 是否需要在锁外发出恢复通知。 /// 需要通知的暂停组。 private readonly record struct PopResult(bool Found, bool ShouldNotify, PauseGroup NotifyGroup) { /// /// 表示未找到目标令牌时的默认结果。 /// public static PopResult NotFound { get; } = new(false, false, PauseGroup.Global); } }