refactor(pause): 将暂停状态变化事件改为标准事件模式

- 将 OnPauseStateChanged 事件从 Action<PauseGroup, bool> 类型改为 EventHandler<PauseStateChangedEventArgs>
- 添加 PauseStateChangedEventArgs 类来封装事件数据
- 更新所有事件处理方法的签名以匹配新的事件参数
- 修改文档中相关的事件处理代码示例
- 在 PauseStackManager 中添加 RaisePauseStateChanged 方法统一处理事件触发
- 更新测试代码以适应新的事件处理方式
This commit is contained in:
GeWuYou 2026-03-21 21:02:41 +08:00 committed by gewuyou
parent 86645d34cb
commit f3d45169cd
14 changed files with 178 additions and 99 deletions

View File

@ -0,0 +1,46 @@
// Copyright (c) 2025 GeWuYou
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Runtime.InteropServices;
namespace GFramework.Core.Abstractions.Concurrency;
/// <summary>
/// 锁信息(用于调试)
/// </summary>
[StructLayout(LayoutKind.Auto)]
public readonly struct LockInfo
{
/// <summary>
/// 锁的键。
/// </summary>
public string Key { get; init; }
/// <summary>
/// 当前引用计数。
/// </summary>
public int ReferenceCount { get; init; }
/// <summary>
/// 最后访问时间戳Environment.TickCount64
/// </summary>
public long LastAccessTicks { get; init; }
/// <summary>
/// 等待队列长度(近似值)。
/// 注意:这是一个基于 SemaphoreSlim.CurrentCount 的近似指示器,
/// 当 CurrentCount == 0 时表示锁被持有且可能有等待者,返回 1
/// 否则返回 0。这不是精确的等待者数量仅用于调试参考。
/// </summary>
public int WaitingCount { get; init; }
}

View File

@ -11,11 +11,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
using System.Runtime.InteropServices;
namespace GFramework.Core.Abstractions.Concurrency;
/// <summary>
/// 锁统计信息
/// </summary>
[StructLayout(LayoutKind.Auto)]
public readonly struct LockStatistics
{
/// <summary>
@ -37,33 +40,4 @@ public readonly struct LockStatistics
/// 累计清理的锁数量
/// </summary>
public int TotalCleaned { get; init; }
}
/// <summary>
/// 锁信息(用于调试)
/// </summary>
public readonly struct LockInfo
{
/// <summary>
/// 锁的键
/// </summary>
public string Key { get; init; }
/// <summary>
/// 当前引用计数
/// </summary>
public int ReferenceCount { get; init; }
/// <summary>
/// 最后访问时间戳Environment.TickCount64
/// </summary>
public long LastAccessTicks { get; init; }
/// <summary>
/// 等待队列长度(近似值)
/// 注意:这是一个基于 SemaphoreSlim.CurrentCount 的近似指示器,
/// 当 CurrentCount == 0 时表示锁被持有且可能有等待者,返回 1
/// 否则返回 0。这不是精确的等待者数量仅用于调试参考。
/// </summary>
public int WaitingCount { get; init; }
}

View File

@ -75,7 +75,9 @@ public interface IPauseStackManager : IContextUtility
void UnregisterHandler(IPauseHandler handler);
/// <summary>
/// 暂停状态变化事件
/// 暂停状态变化事件。
/// 事件遵循标准 .NET 事件模式,事件源为触发通知的暂停管理器实例,
/// 事件数据由 <see cref="PauseStateChangedEventArgs"/> 提供。
/// </summary>
event Action<PauseGroup, bool>? OnPauseStateChanged;
event EventHandler<PauseStateChangedEventArgs>? OnPauseStateChanged;
}

View File

@ -0,0 +1,30 @@
namespace GFramework.Core.Abstractions.Pause;
/// <summary>
/// 表示暂停状态变化事件的数据。
/// 该类型用于向事件订阅者传递暂停组以及该组变化后的暂停状态。
/// </summary>
public sealed class PauseStateChangedEventArgs : EventArgs
{
/// <summary>
/// 初始化 <see cref="PauseStateChangedEventArgs"/> 的新实例。
/// </summary>
/// <param name="group">发生状态变化的暂停组。</param>
/// <param name="isPaused">暂停组变化后的新状态。</param>
public PauseStateChangedEventArgs(PauseGroup group, bool isPaused)
{
Group = group;
IsPaused = isPaused;
}
/// <summary>
/// 获取发生状态变化的暂停组。
/// </summary>
public PauseGroup Group { get; }
/// <summary>
/// 获取暂停组变化后的新状态。
/// 为 <see langword="true"/> 表示进入暂停,为 <see langword="false"/> 表示恢复运行。
/// </summary>
public bool IsPaused { get; }
}

View File

@ -1,6 +1,5 @@
using GFramework.Core.Abstractions.Pause;
using GFramework.Core.Pause;
using NUnit.Framework;
namespace GFramework.Core.Tests.Pause;
@ -220,11 +219,11 @@ public class PauseStackManagerTests
PauseGroup? eventGroup = null;
bool? eventIsPaused = null;
_manager.OnPauseStateChanged += (group, isPaused) =>
_manager.OnPauseStateChanged += (_, e) =>
{
eventTriggered = true;
eventGroup = group;
eventIsPaused = isPaused;
eventGroup = e.Group;
eventIsPaused = e.IsPaused;
};
_manager.Push("Test", PauseGroup.Gameplay);
@ -243,10 +242,10 @@ public class PauseStackManagerTests
var token = _manager.Push("Test");
bool eventTriggered = false;
_manager.OnPauseStateChanged += (group, isPaused) =>
_manager.OnPauseStateChanged += (_, e) =>
{
eventTriggered = true;
Assert.That(isPaused, Is.False);
Assert.That(e.IsPaused, Is.False);
};
_manager.Pop(token);

View File

@ -17,11 +17,6 @@ internal sealed class CoroutineSlot
/// </summary>
public CoroutineHandle Handle;
/// <summary>
/// 协程是否已经开始执行
/// </summary>
public bool HasStarted;
/// <summary>
/// 协程的优先级
/// </summary>

View File

@ -208,13 +208,10 @@ public class PriorityEvent<T> : IEvent
MergeAndSortHandlers(T t)
{
var (normalSnapshot, contextSnapshot) = CreateSnapshots();
// 使用快照避免迭代期间修改
// 使用统一的投影方法显式固定元组的可空标注,避免 LINQ 在 Concat 时推断出不兼容的签名。
return normalSnapshot
.Select(h => (h.Priority, Handler: (Action?)(() => h.Handler.Invoke(t)),
ContextHandler: (Action<EventContext<T>>?)null, IsContext: false))
.Concat(contextSnapshot
.Select(h => (h.Priority, Handler: (Action?)null,
ContextHandler: (Action<EventContext<T>>?)h.Handler, IsContext: true)))
.Select(h => CreateNormalHandlerInvocation(h, t))
.Concat(contextSnapshot.Select(CreateContextHandlerInvocation))
.OrderByDescending(h => h.Priority)
.ToList();
}
@ -289,6 +286,29 @@ public class PriorityEvent<T> : IEvent
}
}
/// <summary>
/// 将普通事件处理器转换为统一的调用描述。
/// </summary>
/// <param name="handler">要包装的普通处理器。</param>
/// <param name="t">当前触发的事件数据。</param>
/// <returns>可与上下文处理器合并排序的统一调用描述。</returns>
private static (int Priority, Action? Handler, Action<EventContext<T>>? ContextHandler, bool IsContext)
CreateNormalHandlerInvocation(EventHandler handler, T t)
{
return (handler.Priority, () => handler.Handler.Invoke(t), null, false);
}
/// <summary>
/// 将上下文事件处理器转换为统一的调用描述。
/// </summary>
/// <param name="handler">要包装的上下文处理器。</param>
/// <returns>可与普通处理器合并排序的统一调用描述。</returns>
private static (int Priority, Action? Handler, Action<EventContext<T>>? ContextHandler, bool IsContext)
CreateContextHandlerInvocation(ContextEventHandler handler)
{
return (handler.Priority, null, handler.Handler, true);
}
/// <summary>
/// 事件处理器包装类,包含处理器和优先级
/// </summary>

View File

@ -80,7 +80,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
// 触发事件
try
{
OnPauseStateChanged?.Invoke(group, false);
RaisePauseStateChanged(group, false);
}
catch (Exception ex)
{
@ -97,7 +97,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
/// <summary>
/// 暂停状态变化事件,当暂停状态发生改变时触发。
/// </summary>
public event Action<PauseGroup, bool>? OnPauseStateChanged;
public event EventHandler<PauseStateChangedEventArgs>? OnPauseStateChanged;
/// <summary>
/// 推入一个新的暂停请求到指定的暂停组中。
@ -488,7 +488,18 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
}
// 触发事件
OnPauseStateChanged?.Invoke(group, isPaused);
RaisePauseStateChanged(group, isPaused);
}
/// <summary>
/// 以标准事件模式发布暂停状态变化事件。
/// 所有状态变更路径都通过该方法创建统一的事件参数,避免不同调用点出现不一致的载荷。
/// </summary>
/// <param name="group">发生状态变化的暂停组。</param>
/// <param name="isPaused">暂停组变化后的新状态。</param>
private void RaisePauseStateChanged(PauseGroup group, bool isPaused)
{
OnPauseStateChanged?.Invoke(this, new PauseStateChangedEventArgs(group, isPaused));
}
/// <summary>

View File

@ -23,9 +23,9 @@ public interface ISceneBehavior : IRoute
{
/// <summary>
/// 获取场景的唯一标识符。
/// 用于区分不同的场景实例。
/// 该成员显式细化了 <see cref="IRoute.Key"/> 在场景路由中的语义,用于区分不同的场景实例。
/// </summary>
string Key { get; }
new string Key { get; }
/// <summary>
/// 获取场景的原始对象。

View File

@ -31,8 +31,9 @@ public interface ISceneRouteGuard : IRouteGuard<ISceneBehavior>
/// <summary>
/// 异步检查是否允许离开指定场景。
/// 该成员显式细化了通用路由守卫的离开检查,使场景守卫在 API 文档中保持场景语义。
/// </summary>
/// <param name="sceneKey">当前场景的唯一标识符。</param>
/// <returns>如果允许离开则返回 true否则返回 false。</returns>
ValueTask<bool> CanLeaveAsync(string sceneKey);
new ValueTask<bool> CanLeaveAsync(string sceneKey);
}

View File

@ -48,10 +48,10 @@ public interface IUiPageBehavior : IRoute
/// <summary>
/// 获取键值
/// 获取页面的唯一键值,并显式细化 <see cref="IRoute.Key"/> 在 UI 路由中的语义。
/// </summary>
/// <value>返回当前对象的键标识符</value>
string Key { get; }
new string Key { get; }
/// <summary>

View File

@ -17,9 +17,10 @@ public interface IUiRouteGuard : IRouteGuard<IUiPageBehavior>
ValueTask<bool> CanEnterAsync(string uiKey, IUiPageEnterParam? param);
/// <summary>
/// 离开UI前的检查
/// 离开UI前的检查。
/// 该成员显式细化了通用路由守卫的离开检查,使 UI 守卫在 API 文档中保持 UI 语义。
/// </summary>
/// <param name="uiKey">当前UI标识符</param>
/// <returns>true表示允许离开false表示拦截</returns>
ValueTask<bool> CanLeaveAsync(string uiKey);
new ValueTask<bool> CanLeaveAsync(string uiKey);
}

View File

@ -100,8 +100,8 @@ void ClearAll();
void RegisterHandler(IPauseHandler handler);
void UnregisterHandler(IPauseHandler handler);
// 状态变化事件
event Action<PauseGroup, bool>? OnPauseStateChanged;
// 状态变化事件
event EventHandler<PauseStateChangedEventArgs>? OnPauseStateChanged;
```
## 基本用法
@ -377,16 +377,16 @@ public partial class PauseIndicator : IController
_pauseManager.OnPauseStateChanged += OnPauseStateChanged;
}
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
{
Console.WriteLine($"暂停状态变化: 组={group}, 暂停={isPaused}");
if (group == PauseGroup.Global)
{
if (isPaused)
{
ShowPauseIndicator();
}
private void OnPauseStateChanged(object? sender, PauseStateChangedEventArgs e)
{
Console.WriteLine($"暂停状态变化: 组={e.Group}, 暂停={e.IsPaused}");
if (e.Group == PauseGroup.Global)
{
if (e.IsPaused)
{
ShowPauseIndicator();
}
else
{
HidePauseIndicator();
@ -705,7 +705,7 @@ public partial class ProperCleanup : IController
_pauseManager.OnPauseStateChanged -= OnPauseChanged;
}
private void OnPauseChanged(PauseGroup group, bool isPaused) { }
private void OnPauseChanged(object? sender, PauseStateChangedEventArgs e) { }
}
```
@ -743,13 +743,13 @@ public partial class PauseMenu : Control
// 方案 2: 监听暂停事件
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.OnPauseStateChanged += (group, isPaused) =>
{
if (group == PauseGroup.Global)
{
Visible = isPaused;
}
};
pauseManager.OnPauseStateChanged += (_, e) =>
{
if (e.Group == PauseGroup.Global)
{
Visible = e.IsPaused;
}
};
}
}
```
@ -887,15 +887,15 @@ public class PauseEventBridge : AbstractSystem
{
var pauseManager = this.GetUtility<IPauseStackManager>();
pauseManager.OnPauseStateChanged += (group, isPaused) =>
{
// 发送暂停事件
this.SendEvent(new GamePausedEvent
{
Group = group,
IsPaused = isPaused
});
};
pauseManager.OnPauseStateChanged += (_, e) =>
{
// 发送暂停事件
this.SendEvent(new GamePausedEvent
{
Group = e.Group,
IsPaused = e.IsPaused
});
};
}
}

View File

@ -44,7 +44,7 @@ public interface IPauseStackManager : IContextUtility
int GetPauseDepth(PauseGroup group = PauseGroup.Global);
// 暂停状态变化事件
event Action<PauseGroup, bool>? OnPauseStateChanged;
event EventHandler<PauseStateChangedEventArgs>? OnPauseStateChanged;
}
```
@ -477,12 +477,12 @@ public partial class PauseIndicator : Label
pauseManager.OnPauseStateChanged -= OnPauseStateChanged;
}
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
private void OnPauseStateChanged(object? sender, PauseStateChangedEventArgs e)
{
if (group == PauseGroup.Global)
if (e.Group == PauseGroup.Global)
{
Text = isPaused ? "游戏已暂停" : "游戏运行中";
Visible = isPaused;
Text = e.IsPaused ? "游戏已暂停" : "游戏运行中";
Visible = e.IsPaused;
}
}
}
@ -502,16 +502,16 @@ public partial class PauseDebugger : Node
pauseManager.OnPauseStateChanged += OnPauseStateChanged;
}
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
private void OnPauseStateChanged(object? sender, PauseStateChangedEventArgs e)
{
var pauseManager = this.GetUtility<IPauseStackManager>();
GD.Print($"=== 暂停状态变化 ===");
GD.Print($"组: {group}");
GD.Print($"状态: {(isPaused ? "暂停" : "恢复")}");
GD.Print($"深度: {pauseManager.GetPauseDepth(group)}");
GD.Print($"组: {e.Group}");
GD.Print($"状态: {(e.IsPaused ? "暂停" : "恢复")}");
GD.Print($"深度: {pauseManager.GetPauseDepth(e.Group)}");
var reasons = pauseManager.GetPauseReasons(group);
var reasons = pauseManager.GetPauseReasons(e.Group);
if (reasons.Count > 0)
{
GD.Print($"原因:");
@ -609,9 +609,9 @@ public partial class PauseDebugger : Node
7. **使用事件监听暂停状态**:实现响应式 UI
```csharp
pauseManager.OnPauseStateChanged += (group, isPaused) =>
pauseManager.OnPauseStateChanged += (_, e) =>
{
UpdateUI(isPaused);
UpdateUI(e.IsPaused);
};
```