From 7734fba56f96c01b5fbc3f7e046bd7da1b549a7f Mon Sep 17 00:00:00 2001
From: GeWuYou <95328647+GeWuYou@users.noreply.github.com>
Date: Fri, 27 Feb 2026 23:29:09 +0800
Subject: [PATCH] =?UTF-8?q?feat(pause):=20=E6=B7=BB=E5=8A=A0=E6=9A=82?=
=?UTF-8?q?=E5=81=9C=E6=A0=88=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
- 实现了 PauseStackManager 核心管理器,支持嵌套暂停和分组管理
- 添加了 PauseToken 暂停令牌和 PauseGroup 暂停组枚举
- 创建了 PauseScope 作用域类,支持 using 语法自动管理暂停生命周期
- 实现了线程安全的暂停栈操作,包括 Push、Pop 和状态查询
- 添加了暂停处理器接口 IPauseHandler 和 Godot 平台具体实现
- 提供了完整的单元测试覆盖基础功能、嵌套暂停、分组管理和线程安全场景
---
.../pause/IPauseHandler.cs | 19 +
.../pause/IPauseStackManager.cs | 81 +++
.../pause/PauseGroup.cs | 42 ++
.../pause/PauseToken.cs | 43 ++
.../pause/PauseStackManagerTests.cs | 473 ++++++++++++++++++
GFramework.Core/pause/PauseEntry.cs | 29 ++
GFramework.Core/pause/PauseScope.cs | 51 ++
GFramework.Core/pause/PauseStackManager.cs | 364 ++++++++++++++
GFramework.Godot/pause/GodotPauseHandler.cs | 42 ++
9 files changed, 1144 insertions(+)
create mode 100644 GFramework.Core.Abstractions/pause/IPauseHandler.cs
create mode 100644 GFramework.Core.Abstractions/pause/IPauseStackManager.cs
create mode 100644 GFramework.Core.Abstractions/pause/PauseGroup.cs
create mode 100644 GFramework.Core.Abstractions/pause/PauseToken.cs
create mode 100644 GFramework.Core.Tests/pause/PauseStackManagerTests.cs
create mode 100644 GFramework.Core/pause/PauseEntry.cs
create mode 100644 GFramework.Core/pause/PauseScope.cs
create mode 100644 GFramework.Core/pause/PauseStackManager.cs
create mode 100644 GFramework.Godot/pause/GodotPauseHandler.cs
diff --git a/GFramework.Core.Abstractions/pause/IPauseHandler.cs b/GFramework.Core.Abstractions/pause/IPauseHandler.cs
new file mode 100644
index 0000000..f8ac3fe
--- /dev/null
+++ b/GFramework.Core.Abstractions/pause/IPauseHandler.cs
@@ -0,0 +1,19 @@
+namespace GFramework.Core.Abstractions.pause;
+
+///
+/// 暂停处理器接口,由引擎层实现具体的暂停/恢复逻辑
+///
+public interface IPauseHandler
+{
+ ///
+ /// 处理器优先级(数值越小优先级越高)
+ ///
+ int Priority { get; }
+
+ ///
+ /// 当某个组的暂停状态变化时调用
+ ///
+ /// 暂停组
+ /// 是否暂停
+ void OnPauseStateChanged(PauseGroup group, bool isPaused);
+}
\ No newline at end of file
diff --git a/GFramework.Core.Abstractions/pause/IPauseStackManager.cs b/GFramework.Core.Abstractions/pause/IPauseStackManager.cs
new file mode 100644
index 0000000..ae06c9b
--- /dev/null
+++ b/GFramework.Core.Abstractions/pause/IPauseStackManager.cs
@@ -0,0 +1,81 @@
+using GFramework.Core.Abstractions.utility;
+
+namespace GFramework.Core.Abstractions.pause;
+
+///
+/// 暂停栈管理器接口,管理嵌套暂停状态
+///
+public interface IPauseStackManager : IContextUtility
+{
+ ///
+ /// 推入暂停请求
+ ///
+ /// 暂停原因(用于调试)
+ /// 暂停组
+ /// 暂停令牌(用于后续恢复)
+ PauseToken Push(string reason, PauseGroup group = PauseGroup.Global);
+
+ ///
+ /// 弹出暂停请求
+ ///
+ /// 暂停令牌
+ /// 是否成功弹出
+ bool Pop(PauseToken token);
+
+ ///
+ /// 查询指定组是否暂停
+ ///
+ /// 暂停组
+ /// 是否暂停
+ bool IsPaused(PauseGroup group = PauseGroup.Global);
+
+ ///
+ /// 获取指定组的暂停深度(栈中元素数量)
+ ///
+ /// 暂停组
+ /// 暂停深度
+ int GetPauseDepth(PauseGroup group = PauseGroup.Global);
+
+ ///
+ /// 获取指定组的所有暂停原因
+ ///
+ /// 暂停组
+ /// 暂停原因列表
+ IReadOnlyList GetPauseReasons(PauseGroup group = PauseGroup.Global);
+
+ ///
+ /// 创建暂停作用域(支持 using 语法)
+ ///
+ /// 暂停原因
+ /// 暂停组
+ /// 可释放的作用域对象
+ IDisposable PauseScope(string reason, PauseGroup group = PauseGroup.Global);
+
+ ///
+ /// 清空指定组的所有暂停请求
+ ///
+ /// 暂停组
+ void ClearGroup(PauseGroup group);
+
+ ///
+ /// 清空所有暂停请求
+ ///
+ void ClearAll();
+
+ ///
+ /// 注册暂停处理器
+ ///
+ /// 处理器实例
+ void RegisterHandler(IPauseHandler handler);
+
+ ///
+ /// 注销暂停处理器
+ ///
+ /// 处理器实例
+ void UnregisterHandler(IPauseHandler handler);
+
+ ///
+ /// 暂停状态变化事件
+ ///
+ event Action? OnPauseStateChanged;
+}
\ No newline at end of file
diff --git a/GFramework.Core.Abstractions/pause/PauseGroup.cs b/GFramework.Core.Abstractions/pause/PauseGroup.cs
new file mode 100644
index 0000000..68bbf15
--- /dev/null
+++ b/GFramework.Core.Abstractions/pause/PauseGroup.cs
@@ -0,0 +1,42 @@
+namespace GFramework.Core.Abstractions.pause;
+
+///
+/// 暂停组枚举,定义不同的暂停作用域
+///
+public enum PauseGroup
+{
+ ///
+ /// 全局暂停(影响所有系统)
+ ///
+ Global = 0,
+
+ ///
+ /// 游戏逻辑暂停(不影响 UI)
+ ///
+ Gameplay = 1,
+
+ ///
+ /// 动画暂停
+ ///
+ Animation = 2,
+
+ ///
+ /// 音频暂停
+ ///
+ Audio = 3,
+
+ ///
+ /// 自定义组 1
+ ///
+ Custom1 = 10,
+
+ ///
+ /// 自定义组 2
+ ///
+ Custom2 = 11,
+
+ ///
+ /// 自定义组 3
+ ///
+ Custom3 = 12
+}
\ No newline at end of file
diff --git a/GFramework.Core.Abstractions/pause/PauseToken.cs b/GFramework.Core.Abstractions/pause/PauseToken.cs
new file mode 100644
index 0000000..1b27802
--- /dev/null
+++ b/GFramework.Core.Abstractions/pause/PauseToken.cs
@@ -0,0 +1,43 @@
+namespace GFramework.Core.Abstractions.pause;
+
+///
+/// 暂停令牌,唯一标识一个暂停请求
+///
+public readonly struct PauseToken : IEquatable
+{
+ ///
+ /// 令牌 ID
+ ///
+ public Guid Id { get; }
+
+ ///
+ /// 是否为有效令牌
+ ///
+ public bool IsValid => Id != Guid.Empty;
+
+ ///
+ /// 创建暂停令牌
+ ///
+ /// 令牌 ID
+ public PauseToken(Guid id)
+ {
+ Id = id;
+ }
+
+ ///
+ /// 创建无效令牌
+ ///
+ public static PauseToken Invalid => new(Guid.Empty);
+
+ public bool Equals(PauseToken other) => Id.Equals(other.Id);
+
+ public override bool Equals(object? obj) => obj is PauseToken other && Equals(other);
+
+ public override int GetHashCode() => Id.GetHashCode();
+
+ public static bool operator ==(PauseToken left, PauseToken right) => left.Equals(right);
+
+ public static bool operator !=(PauseToken left, PauseToken right) => !left.Equals(right);
+
+ public override string ToString() => $"PauseToken({Id})";
+}
\ No newline at end of file
diff --git a/GFramework.Core.Tests/pause/PauseStackManagerTests.cs b/GFramework.Core.Tests/pause/PauseStackManagerTests.cs
new file mode 100644
index 0000000..79b77d2
--- /dev/null
+++ b/GFramework.Core.Tests/pause/PauseStackManagerTests.cs
@@ -0,0 +1,473 @@
+using GFramework.Core.Abstractions.pause;
+using GFramework.Core.pause;
+using NUnit.Framework;
+
+namespace GFramework.Core.Tests.pause;
+
+///
+/// 暂停栈管理器单元测试
+///
+[TestFixture]
+public class PauseStackManagerTests
+{
+ ///
+ /// 在每个测试方法执行前设置测试环境
+ ///
+ [SetUp]
+ public void SetUp()
+ {
+ _manager = new PauseStackManager();
+ }
+
+ ///
+ /// 在每个测试方法执行后清理资源
+ ///
+ [TearDown]
+ public void TearDown()
+ {
+ _manager.DestroyAsync();
+ }
+
+ private PauseStackManager _manager = null!;
+
+ ///
+ /// 验证Push方法返回有效的令牌
+ ///
+ [Test]
+ public void Push_Should_ReturnValidToken()
+ {
+ var token = _manager.Push("Test pause");
+
+ Assert.That(token.IsValid, Is.True);
+ Assert.That(token.Id, Is.Not.EqualTo(Guid.Empty));
+ }
+
+ ///
+ /// 验证Push方法设置暂停状态
+ ///
+ [Test]
+ public void Push_Should_SetPausedState()
+ {
+ Assert.That(_manager.IsPaused(), Is.False);
+
+ _manager.Push("Test pause");
+
+ Assert.That(_manager.IsPaused(), Is.True);
+ }
+
+ ///
+ /// 验证Pop方法清除暂停状态
+ ///
+ [Test]
+ public void Pop_Should_ClearPausedState()
+ {
+ var token = _manager.Push("Test pause");
+ Assert.That(_manager.IsPaused(), Is.True);
+
+ _manager.Pop(token);
+
+ Assert.That(_manager.IsPaused(), Is.False);
+ }
+
+ ///
+ /// 验证Pop无效令牌返回false
+ ///
+ [Test]
+ public void Pop_WithInvalidToken_Should_ReturnFalse()
+ {
+ var result = _manager.Pop(PauseToken.Invalid);
+
+ Assert.That(result, Is.False);
+ }
+
+ ///
+ /// 验证Pop过期令牌返回false
+ ///
+ [Test]
+ public void Pop_WithExpiredToken_Should_ReturnFalse()
+ {
+ var token = _manager.Push("Test pause");
+ _manager.Pop(token);
+
+ // 尝试再次 Pop 同一个 Token
+ var result = _manager.Pop(token);
+
+ Assert.That(result, Is.False);
+ }
+
+ ///
+ /// 验证多次Push增加深度
+ ///
+ [Test]
+ public void MultiplePush_Should_IncreaseDepth()
+ {
+ _manager.Push("First");
+ _manager.Push("Second");
+ _manager.Push("Third");
+
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(3));
+ }
+
+ ///
+ /// 验证嵌套暂停需要所有Pop才能恢复
+ ///
+ [Test]
+ public void NestedPause_Should_RequireAllPops()
+ {
+ var token1 = _manager.Push("First");
+ var token2 = _manager.Push("Second");
+
+ _manager.Pop(token1);
+ Assert.That(_manager.IsPaused(), Is.True, "Should still be paused after first pop");
+
+ _manager.Pop(token2);
+ Assert.That(_manager.IsPaused(), Is.False, "Should be unpaused after all pops");
+ }
+
+ ///
+ /// 验证Pop非栈顶令牌可以正常工作
+ ///
+ [Test]
+ public void Pop_WithNonTopToken_Should_Work()
+ {
+ var token1 = _manager.Push("First");
+ var token2 = _manager.Push("Second");
+ var token3 = _manager.Push("Third");
+
+ // Pop 中间的 token
+ var result = _manager.Pop(token2);
+
+ Assert.That(result, Is.True);
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(2));
+ Assert.That(_manager.IsPaused(), Is.True);
+
+ // 验证剩余的令牌仍然有效
+ Assert.That(_manager.Pop(token1), Is.True);
+ Assert.That(_manager.Pop(token3), Is.True);
+ Assert.That(_manager.IsPaused(), Is.False);
+ }
+
+ ///
+ /// 验证不同组独立工作
+ ///
+ [Test]
+ public void DifferentGroups_Should_BeIndependent()
+ {
+ _manager.Push("Global pause", PauseGroup.Global);
+ _manager.Push("Gameplay pause", PauseGroup.Gameplay);
+
+ Assert.That(_manager.IsPaused(PauseGroup.Global), Is.True);
+ Assert.That(_manager.IsPaused(PauseGroup.Gameplay), Is.True);
+ Assert.That(_manager.IsPaused(PauseGroup.Audio), Is.False);
+ }
+
+ ///
+ /// 验证Pop只影响正确的组
+ ///
+ [Test]
+ public void Pop_Should_OnlyAffectCorrectGroup()
+ {
+ var globalToken = _manager.Push("Global");
+ var gameplayToken = _manager.Push("Gameplay", PauseGroup.Gameplay);
+
+ _manager.Pop(globalToken);
+
+ Assert.That(_manager.IsPaused(), Is.False);
+ Assert.That(_manager.IsPaused(PauseGroup.Gameplay), Is.True);
+
+ // 验证 gameplayToken 仍然有效并且可以被正常弹出
+ Assert.That(_manager.Pop(gameplayToken), Is.True);
+ Assert.That(_manager.IsPaused(PauseGroup.Gameplay), Is.False);
+ }
+
+
+ ///
+ /// 验证GetPauseReasons返回所有原因
+ ///
+ [Test]
+ public void GetPauseReasons_Should_ReturnAllReasons()
+ {
+ _manager.Push("Menu opened");
+ _manager.Push("Dialog shown");
+ _manager.Push("Inventory opened");
+
+ var reasons = _manager.GetPauseReasons();
+
+ Assert.That(reasons.Count, Is.EqualTo(3));
+ Assert.That(reasons, Does.Contain("Menu opened"));
+ Assert.That(reasons, Does.Contain("Dialog shown"));
+ Assert.That(reasons, Does.Contain("Inventory opened"));
+ }
+
+ ///
+ /// 验证空栈GetPauseReasons返回空列表
+ ///
+ [Test]
+ public void GetPauseReasons_WithEmptyStack_Should_ReturnEmptyList()
+ {
+ var reasons = _manager.GetPauseReasons();
+
+ Assert.That(reasons, Is.Empty);
+ }
+
+ ///
+ /// 验证Push触发状态变化事件
+ ///
+ [Test]
+ public void Push_Should_TriggerEventWhenStateChanges()
+ {
+ bool eventTriggered = false;
+ PauseGroup? eventGroup = null;
+ bool? eventIsPaused = null;
+
+ _manager.OnPauseStateChanged += (group, isPaused) =>
+ {
+ eventTriggered = true;
+ eventGroup = group;
+ eventIsPaused = isPaused;
+ };
+
+ _manager.Push("Test", PauseGroup.Gameplay);
+
+ Assert.That(eventTriggered, Is.True);
+ Assert.That(eventGroup, Is.EqualTo(PauseGroup.Gameplay));
+ Assert.That(eventIsPaused, Is.True);
+ }
+
+ ///
+ /// 验证Pop在栈变空时触发事件
+ ///
+ [Test]
+ public void Pop_Should_TriggerEventWhenStackBecomesEmpty()
+ {
+ var token = _manager.Push("Test");
+
+ bool eventTriggered = false;
+ _manager.OnPauseStateChanged += (group, isPaused) =>
+ {
+ eventTriggered = true;
+ Assert.That(isPaused, Is.False);
+ };
+
+ _manager.Pop(token);
+ Assert.That(eventTriggered, Is.True);
+ }
+
+ ///
+ /// 验证多次Push只触发一次事件
+ ///
+ [Test]
+ public void MultiplePush_Should_OnlyTriggerEventOnce()
+ {
+ int eventCount = 0;
+ _manager.OnPauseStateChanged += (_, _) => eventCount++;
+
+ _manager.Push("First");
+ _manager.Push("Second");
+
+ Assert.That(eventCount, Is.EqualTo(1));
+ }
+
+ ///
+ /// 验证处理器在状态变化时被通知
+ ///
+ [Test]
+ public void Handler_Should_BeNotifiedOnStateChange()
+ {
+ var mockHandler = new MockPauseHandler();
+ _manager.RegisterHandler(mockHandler);
+
+ _manager.Push("Test", PauseGroup.Global);
+
+ Assert.That(mockHandler.CallCount, Is.EqualTo(1));
+ Assert.That(mockHandler.LastGroup, Is.EqualTo(PauseGroup.Global));
+ Assert.That(mockHandler.LastIsPaused, Is.True);
+ }
+
+ ///
+ /// 验证处理器在恢复时被通知
+ ///
+ [Test]
+ public void Handler_Should_BeNotifiedOnResume()
+ {
+ var mockHandler = new MockPauseHandler();
+ _manager.RegisterHandler(mockHandler);
+
+ var token = _manager.Push("Test");
+ mockHandler.Reset();
+
+ _manager.Pop(token);
+
+ Assert.That(mockHandler.CallCount, Is.EqualTo(1));
+ Assert.That(mockHandler.LastIsPaused, Is.False);
+ }
+
+ ///
+ /// 验证多个处理器按优先级顺序调用
+ ///
+ [Test]
+ public void MultipleHandlers_Should_BeCalledInPriorityOrder()
+ {
+ var calls = new List();
+
+ var handler1 = new MockPauseHandler { Priority = 10 };
+ var handler2 = new MockPauseHandler { Priority = 5 };
+ var handler3 = new MockPauseHandler { Priority = 15 };
+
+ handler1.OnCall = () => calls.Add(1);
+ handler2.OnCall = () => calls.Add(2);
+ handler3.OnCall = () => calls.Add(3);
+
+ _manager.RegisterHandler(handler1);
+ _manager.RegisterHandler(handler2);
+ _manager.RegisterHandler(handler3);
+
+ _manager.Push("Test");
+
+ Assert.That(calls, Is.EqualTo(new[] { 2, 1, 3 }));
+ }
+
+ ///
+ /// 验证PauseScope在Dispose时自动恢复
+ ///
+ [Test]
+ public void PauseScope_Should_AutoResumeOnDispose()
+ {
+ using (_manager.PauseScope("Test"))
+ {
+ Assert.That(_manager.IsPaused(), Is.True);
+ }
+
+ Assert.That(_manager.IsPaused(), Is.False);
+ }
+
+ ///
+ /// 验证嵌套PauseScope正常工作
+ ///
+ [Test]
+ public void NestedPauseScope_Should_Work()
+ {
+ using (_manager.PauseScope("Outer"))
+ {
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(1));
+
+ using (_manager.PauseScope("Inner"))
+ {
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(2));
+ }
+
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(1));
+ }
+
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(0));
+ }
+
+ ///
+ /// 验证ClearGroup移除指定组的所有暂停
+ ///
+ [Test]
+ public void ClearGroup_Should_RemoveAllPausesForGroup()
+ {
+ _manager.Push("First", PauseGroup.Gameplay);
+ _manager.Push("Second", PauseGroup.Gameplay);
+ _manager.Push("Third", PauseGroup.Audio);
+
+ _manager.ClearGroup(PauseGroup.Gameplay);
+
+ Assert.That(_manager.IsPaused(PauseGroup.Gameplay), Is.False);
+ Assert.That(_manager.IsPaused(PauseGroup.Audio), Is.True);
+ }
+
+ ///
+ /// 验证ClearAll移除所有暂停
+ ///
+ [Test]
+ public void ClearAll_Should_RemoveAllPauses()
+ {
+ _manager.Push("First", PauseGroup.Global);
+ _manager.Push("Second", PauseGroup.Gameplay);
+ _manager.Push("Third", PauseGroup.Audio);
+
+ _manager.ClearAll();
+
+ Assert.That(_manager.IsPaused(PauseGroup.Global), Is.False);
+ Assert.That(_manager.IsPaused(PauseGroup.Gameplay), Is.False);
+ Assert.That(_manager.IsPaused(PauseGroup.Audio), Is.False);
+ }
+
+ ///
+ /// 验证并发Push是线程安全的
+ ///
+ [Test]
+ public void ConcurrentPush_Should_BeThreadSafe()
+ {
+ var tasks = new List();
+ var tokens = new List();
+ var lockObj = new object();
+
+ for (int i = 0; i < 100; i++)
+ {
+ var index = i;
+ tasks.Add(Task.Run(() =>
+ {
+ var token = _manager.Push($"Pause {index}");
+ lock (lockObj)
+ {
+ tokens.Add(token);
+ }
+ }));
+ }
+
+ Task.WaitAll(tasks.ToArray());
+
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(100));
+ Assert.That(tokens.Count, Is.EqualTo(100));
+ }
+
+ ///
+ /// 验证并发Pop是线程安全的
+ ///
+ [Test]
+ public void ConcurrentPop_Should_BeThreadSafe()
+ {
+ var tokens = new List();
+ for (int i = 0; i < 100; i++)
+ {
+ tokens.Add(_manager.Push($"Pause {i}"));
+ }
+
+ var tasks = tokens.Select(token => Task.Run(() => _manager.Pop(token))).ToList();
+
+ Task.WaitAll(tasks.ToArray());
+
+ Assert.That(_manager.GetPauseDepth(), Is.EqualTo(0));
+ Assert.That(_manager.IsPaused(), Is.False);
+ }
+
+ ///
+ /// 测试用的暂停处理器实现
+ ///
+ private class MockPauseHandler : IPauseHandler
+ {
+ public int CallCount { get; private set; }
+ public PauseGroup? LastGroup { get; private set; }
+ public bool? LastIsPaused { get; private set; }
+ public Action? OnCall { get; set; }
+ public int Priority { get; set; } = 0;
+
+ public void OnPauseStateChanged(PauseGroup group, bool isPaused)
+ {
+ CallCount++;
+ LastGroup = group;
+ LastIsPaused = isPaused;
+ OnCall?.Invoke();
+ }
+
+ public void Reset()
+ {
+ CallCount = 0;
+ LastGroup = null;
+ LastIsPaused = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Core/pause/PauseEntry.cs b/GFramework.Core/pause/PauseEntry.cs
new file mode 100644
index 0000000..68737f7
--- /dev/null
+++ b/GFramework.Core/pause/PauseEntry.cs
@@ -0,0 +1,29 @@
+using GFramework.Core.Abstractions.pause;
+
+namespace GFramework.Core.pause;
+
+///
+/// 暂停条目(内部数据结构)
+///
+internal class PauseEntry
+{
+ ///
+ /// 令牌 ID
+ ///
+ public required Guid TokenId { get; init; }
+
+ ///
+ /// 暂停原因
+ ///
+ public required string Reason { get; init; }
+
+ ///
+ /// 暂停组
+ ///
+ public required PauseGroup Group { get; init; }
+
+ ///
+ /// 创建时间戳
+ ///
+ public required DateTime Timestamp { get; init; }
+}
\ No newline at end of file
diff --git a/GFramework.Core/pause/PauseScope.cs b/GFramework.Core/pause/PauseScope.cs
new file mode 100644
index 0000000..5040ebc
--- /dev/null
+++ b/GFramework.Core/pause/PauseScope.cs
@@ -0,0 +1,51 @@
+using GFramework.Core.Abstractions.pause;
+
+namespace GFramework.Core.pause;
+
+///
+/// 暂停作用域,支持 using 语法自动管理暂停生命周期
+///
+public class PauseScope : IDisposable
+{
+ private readonly IPauseStackManager _manager;
+ private readonly PauseToken _token;
+ private bool _disposed;
+
+ ///
+ /// 创建暂停作用域
+ ///
+ /// 暂停栈管理器
+ /// 暂停原因
+ /// 暂停组
+ public PauseScope(IPauseStackManager manager, string reason, PauseGroup group = PauseGroup.Global)
+ {
+ _manager = manager ?? throw new ArgumentNullException(nameof(manager));
+ _token = _manager.Push(reason, group);
+ }
+
+ ///
+ /// 释放作用域,自动恢复暂停
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// 释放资源
+ ///
+ /// 是否正在显式释放
+ protected virtual void Dispose(bool disposing)
+ {
+ if (_disposed)
+ return;
+
+ if (disposing)
+ {
+ _manager.Pop(_token);
+ }
+
+ _disposed = true;
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Core/pause/PauseStackManager.cs b/GFramework.Core/pause/PauseStackManager.cs
new file mode 100644
index 0000000..e561bdf
--- /dev/null
+++ b/GFramework.Core/pause/PauseStackManager.cs
@@ -0,0 +1,364 @@
+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();
+
+ ///
+ /// 异步销毁方法,在组件销毁时调用。
+ ///
+ /// 表示异步操作完成的任务。
+ public ValueTask DestroyAsync()
+ {
+ _lock.Dispose();
+ return ValueTask.CompletedTask;
+ }
+
+ ///
+ /// 暂停状态变化事件,当暂停状态发生改变时触发。
+ ///
+ public event Action? OnPauseStateChanged;
+
+ ///
+ /// 推入一个新的暂停请求到指定的暂停组中。
+ ///
+ /// 暂停的原因描述。
+ /// 暂停组,默认为全局暂停组。
+ /// 表示此次暂停请求的令牌。
+ public PauseToken Push(string reason, PauseGroup group = PauseGroup.Global)
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ 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})");
+
+ // 状态变化检测:从未暂停 → 暂停
+ if (!wasPaused)
+ {
+ NotifyHandlers(group, true);
+ }
+
+ return new PauseToken(entry.TokenId);
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ }
+
+ ///
+ /// 弹出指定的暂停请求。
+ ///
+ /// 要弹出的暂停令牌。
+ /// 如果成功弹出则返回true,否则返回false。
+ public bool Pop(PauseToken token)
+ {
+ if (!token.IsValid)
+ return false;
+
+ _lock.EnterWriteLock();
+ try
+ {
+ if (!_tokenMap.TryGetValue(token.Id, out var entry))
+ {
+ _logger.Warn($"Attempted to pop invalid/expired token: {token.Id}");
+ return false;
+ }
+
+ var group = entry.Group;
+ var stack = _pauseStacks[group];
+ var wasPaused = stack.Count > 0;
+
+ // 从栈中移除
+ var tempStack = new Stack();
+ bool found = false;
+
+ while (stack.Count > 0)
+ {
+ var current = stack.Pop();
+ if (current.TokenId == token.Id)
+ {
+ found = true;
+ break;
+ }
+
+ tempStack.Push(current);
+ }
+
+ // 恢复栈结构
+ while (tempStack.Count > 0)
+ {
+ stack.Push(tempStack.Pop());
+ }
+
+ if (found)
+ {
+ _tokenMap.Remove(token.Id);
+ _logger.Debug($"Pause popped: {entry.Reason} (Group: {group}, Remaining: {stack.Count})");
+
+ // 状态变化检测:从暂停 → 未暂停
+ if (wasPaused && stack.Count == 0)
+ {
+ NotifyHandlers(group, false);
+ }
+ }
+
+ return found;
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ }
+
+ ///
+ /// 查询指定暂停组当前是否处于暂停状态。
+ ///
+ /// 要查询的暂停组,默认为全局暂停组。
+ /// 如果该组处于暂停状态则返回true,否则返回false。
+ public bool IsPaused(PauseGroup group = PauseGroup.Global)
+ {
+ _lock.EnterReadLock();
+ try
+ {
+ return IsPausedInternal(group);
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+
+ ///
+ /// 获取指定暂停组的暂停深度(即嵌套暂停的层数)。
+ ///
+ /// 要查询的暂停组,默认为全局暂停组。
+ /// 暂停深度,0表示未暂停。
+ public int GetPauseDepth(PauseGroup group = PauseGroup.Global)
+ {
+ _lock.EnterReadLock();
+ try
+ {
+ return _pauseStacks.TryGetValue(group, out var stack) ? stack.Count : 0;
+ }
+ finally
+ {
+ _lock.ExitReadLock();
+ }
+ }
+
+ ///
+ /// 获取指定暂停组的所有暂停原因。
+ ///
+ /// 要查询的暂停组,默认为全局暂停组。
+ /// 包含所有暂停原因的只读列表。
+ public IReadOnlyList GetPauseReasons(PauseGroup group = PauseGroup.Global)
+ {
+ _lock.EnterReadLock();
+ try
+ {
+ 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)
+ {
+ return new PauseScope(this, reason, group);
+ }
+
+ ///
+ /// 清空指定暂停组的所有暂停请求。
+ ///
+ /// 要清空的暂停组。
+ public void ClearGroup(PauseGroup group)
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ 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}");
+
+ // 状态变化检测
+ if (wasPaused)
+ {
+ NotifyHandlers(group, false);
+ }
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ }
+
+ ///
+ /// 清空所有暂停组的所有暂停请求。
+ ///
+ public void ClearAll()
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ var 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");
+
+ // 通知所有之前暂停的组
+ foreach (var group in pausedGroups)
+ {
+ NotifyHandlers(group, false);
+ }
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ }
+
+ ///
+ /// 注册一个暂停处理器,用于监听暂停状态的变化。
+ ///
+ /// 要注册的暂停处理器。
+ public void RegisterHandler(IPauseHandler handler)
+ {
+ _lock.EnterWriteLock();
+ try
+ {
+ 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
+ {
+ if (_handlers.Remove(handler))
+ {
+ _logger.Debug($"Unregistered pause handler: {handler.GetType().Name}");
+ }
+ }
+ finally
+ {
+ _lock.ExitWriteLock();
+ }
+ }
+
+ ///
+ /// 内部查询暂停状态的方法,不加锁。
+ ///
+ /// 要查询的暂停组。
+ /// 如果该组处于暂停状态则返回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}");
+
+ // 按优先级排序后通知
+ foreach (var handler in _handlers.OrderBy(h => h.Priority))
+ {
+ try
+ {
+ handler.OnPauseStateChanged(group, isPaused);
+ }
+ catch (Exception ex)
+ {
+ _logger.Error($"Handler {handler.GetType().Name} failed", ex);
+ }
+ }
+
+ // 触发事件
+ OnPauseStateChanged?.Invoke(group, isPaused);
+ }
+
+ ///
+ /// 初始化方法,在组件初始化时调用。
+ ///
+ protected override void OnInit()
+ {
+ }
+}
\ No newline at end of file
diff --git a/GFramework.Godot/pause/GodotPauseHandler.cs b/GFramework.Godot/pause/GodotPauseHandler.cs
new file mode 100644
index 0000000..f77368c
--- /dev/null
+++ b/GFramework.Godot/pause/GodotPauseHandler.cs
@@ -0,0 +1,42 @@
+using GFramework.Core.Abstractions.pause;
+using Godot;
+
+namespace GFramework.Godot.pause;
+
+///
+/// Godot 引擎的暂停处理器
+/// 响应暂停栈状态变化,控制 SceneTree.Paused
+///
+public class GodotPauseHandler : IPauseHandler
+{
+ private readonly SceneTree _tree;
+
+ ///
+ /// 创建 Godot 暂停处理器
+ ///
+ /// 场景树
+ public GodotPauseHandler(SceneTree tree)
+ {
+ _tree = tree ?? throw new ArgumentNullException(nameof(tree));
+ }
+
+ ///
+ /// 处理器优先级
+ ///
+ public int Priority => 0;
+
+ ///
+ /// 当暂停状态变化时调用
+ ///
+ /// 暂停组
+ /// 是否暂停
+ public void OnPauseStateChanged(PauseGroup group, bool isPaused)
+ {
+ // 只有 Global 组影响 Godot 的全局暂停
+ if (group == PauseGroup.Global)
+ {
+ _tree.Paused = isPaused;
+ GD.Print($"[GodotPauseHandler] SceneTree.Paused = {isPaused}");
+ }
+ }
+}
\ No newline at end of file