mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-03-22 10:34:30 +08:00
Compare commits
12 Commits
51492b1dcd
...
63b1d71a0e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
63b1d71a0e | ||
|
|
cdc49c319a | ||
|
|
d94d8deb29 | ||
|
|
49609d3821 | ||
|
|
003fe42ad8 | ||
|
|
a42ec0c282 | ||
|
|
884249649d | ||
|
|
d582dffe40 | ||
|
|
63a6c2e6f0 | ||
|
|
f3d45169cd | ||
|
|
86645d34cb | ||
|
|
ab04f0ace7 |
43
GFramework.Core.Abstractions/Concurrency/LockInfo.cs
Normal file
43
GFramework.Core.Abstractions/Concurrency/LockInfo.cs
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace GFramework.Core.Abstractions.Concurrency;
|
||||||
|
|
||||||
|
/// <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; }
|
||||||
|
}
|
||||||
@ -11,11 +11,14 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace GFramework.Core.Abstractions.Concurrency;
|
namespace GFramework.Core.Abstractions.Concurrency;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 锁统计信息
|
/// 锁统计信息
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
[StructLayout(LayoutKind.Auto)]
|
||||||
public readonly struct LockStatistics
|
public readonly struct LockStatistics
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -37,33 +40,4 @@ public readonly struct LockStatistics
|
|||||||
/// 累计清理的锁数量
|
/// 累计清理的锁数量
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TotalCleaned { get; init; }
|
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; }
|
|
||||||
}
|
}
|
||||||
@ -75,7 +75,9 @@ public interface IPauseStackManager : IContextUtility
|
|||||||
void UnregisterHandler(IPauseHandler handler);
|
void UnregisterHandler(IPauseHandler handler);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 暂停状态变化事件
|
/// 暂停状态变化事件。
|
||||||
|
/// 事件遵循标准 .NET 事件模式,事件源为触发通知的暂停管理器实例,
|
||||||
|
/// 事件数据由 <see cref="PauseStateChangedEventArgs"/> 提供。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
event Action<PauseGroup, bool>? OnPauseStateChanged;
|
event EventHandler<PauseStateChangedEventArgs>? OnPauseStateChanged;
|
||||||
}
|
}
|
||||||
@ -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; }
|
||||||
|
}
|
||||||
@ -7,6 +7,7 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<IsPackable>false</IsPackable>
|
<IsPackable>false</IsPackable>
|
||||||
<IsTestProject>true</IsTestProject>
|
<IsTestProject>true</IsTestProject>
|
||||||
|
<WarningLevel>0</WarningLevel>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Mediator.Abstractions" Version="3.0.1"/>
|
<PackageReference Include="Mediator.Abstractions" Version="3.0.1"/>
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
using GFramework.Core.Abstractions.Logging;
|
using GFramework.Core.Abstractions.Logging;
|
||||||
using GFramework.Core.Logging.Appenders;
|
using GFramework.Core.Logging.Appenders;
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Logging;
|
namespace GFramework.Core.Tests.Logging;
|
||||||
|
|
||||||
@ -152,8 +151,12 @@ public class AsyncLogAppenderTests
|
|||||||
[Test]
|
[Test]
|
||||||
public void Append_WhenInnerAppenderThrows_ShouldNotCrash()
|
public void Append_WhenInnerAppenderThrows_ShouldNotCrash()
|
||||||
{
|
{
|
||||||
|
var reportedExceptions = new List<Exception>();
|
||||||
var innerAppender = new ThrowingAppender();
|
var innerAppender = new ThrowingAppender();
|
||||||
using var asyncAppender = new AsyncLogAppender(innerAppender, bufferSize: 1000);
|
using var asyncAppender = new AsyncLogAppender(
|
||||||
|
innerAppender,
|
||||||
|
bufferSize: 1000,
|
||||||
|
processingErrorHandler: reportedExceptions.Add);
|
||||||
|
|
||||||
// 即使内部 Appender 抛出异常,也不应该影响调用线程
|
// 即使内部 Appender 抛出异常,也不应该影响调用线程
|
||||||
Assert.DoesNotThrow(() =>
|
Assert.DoesNotThrow(() =>
|
||||||
@ -165,7 +168,56 @@ public class AsyncLogAppenderTests
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Thread.Sleep(100); // 等待后台处理
|
asyncAppender.Flush();
|
||||||
|
|
||||||
|
Assert.That(reportedExceptions, Has.Count.EqualTo(10));
|
||||||
|
Assert.That(reportedExceptions, Has.All.TypeOf<InvalidOperationException>());
|
||||||
|
Assert.That(reportedExceptions.Select(static exception => exception.Message),
|
||||||
|
Has.All.EqualTo("Test exception"));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Append_WhenProcessingErrorHandlerThrows_ShouldStillNotCrash()
|
||||||
|
{
|
||||||
|
var innerAppender = new ThrowingAppender();
|
||||||
|
using var asyncAppender = new AsyncLogAppender(
|
||||||
|
innerAppender,
|
||||||
|
bufferSize: 1000,
|
||||||
|
processingErrorHandler: static _ => throw new InvalidOperationException("Observer failure"));
|
||||||
|
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
|
||||||
|
asyncAppender.Append(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(asyncAppender.Flush(), Is.True);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void Append_WhenInnerAppenderThrowsOperationCanceledException_ShouldNotReportError()
|
||||||
|
{
|
||||||
|
var reportedExceptions = new List<Exception>();
|
||||||
|
var innerAppender = new CancellationAppender();
|
||||||
|
using var asyncAppender = new AsyncLogAppender(
|
||||||
|
innerAppender,
|
||||||
|
bufferSize: 1000,
|
||||||
|
processingErrorHandler: reportedExceptions.Add);
|
||||||
|
|
||||||
|
Assert.DoesNotThrow(() =>
|
||||||
|
{
|
||||||
|
for (int i = 0; i < 10; i++)
|
||||||
|
{
|
||||||
|
var entry = new LogEntry(DateTime.UtcNow, LogLevel.Info, "TestLogger", $"Message {i}", null, null);
|
||||||
|
asyncAppender.Append(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Assert.That(asyncAppender.Flush(), Is.True);
|
||||||
|
Assert.That(reportedExceptions, Is.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 辅助测试类
|
// 辅助测试类
|
||||||
@ -228,4 +280,20 @@ public class AsyncLogAppenderTests
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class CancellationAppender : ILogAppender
|
||||||
|
{
|
||||||
|
public void Append(LogEntry entry)
|
||||||
|
{
|
||||||
|
throw new OperationCanceledException("Simulated cancellation");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Flush()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
using GFramework.Core.Abstractions.Pause;
|
using GFramework.Core.Abstractions.Pause;
|
||||||
using GFramework.Core.Pause;
|
using GFramework.Core.Pause;
|
||||||
using NUnit.Framework;
|
|
||||||
|
|
||||||
namespace GFramework.Core.Tests.Pause;
|
namespace GFramework.Core.Tests.Pause;
|
||||||
|
|
||||||
@ -220,11 +219,11 @@ public class PauseStackManagerTests
|
|||||||
PauseGroup? eventGroup = null;
|
PauseGroup? eventGroup = null;
|
||||||
bool? eventIsPaused = null;
|
bool? eventIsPaused = null;
|
||||||
|
|
||||||
_manager.OnPauseStateChanged += (group, isPaused) =>
|
_manager.OnPauseStateChanged += (_, e) =>
|
||||||
{
|
{
|
||||||
eventTriggered = true;
|
eventTriggered = true;
|
||||||
eventGroup = group;
|
eventGroup = e.Group;
|
||||||
eventIsPaused = isPaused;
|
eventIsPaused = e.IsPaused;
|
||||||
};
|
};
|
||||||
|
|
||||||
_manager.Push("Test", PauseGroup.Gameplay);
|
_manager.Push("Test", PauseGroup.Gameplay);
|
||||||
@ -243,10 +242,10 @@ public class PauseStackManagerTests
|
|||||||
var token = _manager.Push("Test");
|
var token = _manager.Push("Test");
|
||||||
|
|
||||||
bool eventTriggered = false;
|
bool eventTriggered = false;
|
||||||
_manager.OnPauseStateChanged += (group, isPaused) =>
|
_manager.OnPauseStateChanged += (_, e) =>
|
||||||
{
|
{
|
||||||
eventTriggered = true;
|
eventTriggered = true;
|
||||||
Assert.That(isPaused, Is.False);
|
Assert.That(e.IsPaused, Is.False);
|
||||||
};
|
};
|
||||||
|
|
||||||
_manager.Pop(token);
|
_manager.Pop(token);
|
||||||
|
|||||||
@ -17,11 +17,6 @@ internal sealed class CoroutineSlot
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CoroutineHandle Handle;
|
public CoroutineHandle Handle;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 协程是否已经开始执行
|
|
||||||
/// </summary>
|
|
||||||
public bool HasStarted;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 协程的优先级
|
/// 协程的优先级
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -208,13 +208,10 @@ public class PriorityEvent<T> : IEvent
|
|||||||
MergeAndSortHandlers(T t)
|
MergeAndSortHandlers(T t)
|
||||||
{
|
{
|
||||||
var (normalSnapshot, contextSnapshot) = CreateSnapshots();
|
var (normalSnapshot, contextSnapshot) = CreateSnapshots();
|
||||||
// 使用快照避免迭代期间修改
|
// 使用统一的投影方法显式固定元组的可空标注,避免 LINQ 在 Concat 时推断出不兼容的签名。
|
||||||
return normalSnapshot
|
return normalSnapshot
|
||||||
.Select(h => (h.Priority, Handler: (Action?)(() => h.Handler.Invoke(t)),
|
.Select(h => CreateNormalHandlerInvocation(h, t))
|
||||||
ContextHandler: (Action<EventContext<T>>?)null, IsContext: false))
|
.Concat(contextSnapshot.Select(CreateContextHandlerInvocation))
|
||||||
.Concat(contextSnapshot
|
|
||||||
.Select(h => (h.Priority, Handler: (Action?)null,
|
|
||||||
ContextHandler: (Action<EventContext<T>>?)h.Handler, IsContext: true)))
|
|
||||||
.OrderByDescending(h => h.Priority)
|
.OrderByDescending(h => h.Priority)
|
||||||
.ToList();
|
.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>
|
||||||
/// 事件处理器包装类,包含处理器和优先级
|
/// 事件处理器包装类,包含处理器和优先级
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -15,4 +15,5 @@ global using System;
|
|||||||
global using System.Collections.Generic;
|
global using System.Collections.Generic;
|
||||||
global using System.Linq;
|
global using System.Linq;
|
||||||
global using System.Threading;
|
global using System.Threading;
|
||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
|
global using System.Threading.Channels;
|
||||||
@ -1,17 +1,26 @@
|
|||||||
using System.Threading.Channels;
|
|
||||||
using GFramework.Core.Abstractions.Logging;
|
using GFramework.Core.Abstractions.Logging;
|
||||||
|
|
||||||
namespace GFramework.Core.Logging.Appenders;
|
namespace GFramework.Core.Logging.Appenders;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步日志输出器,使用 Channel 实现非阻塞日志写入
|
/// 异步日志输出器,使用 <see cref="Channel" /> 将调用线程与慢速日志目标解耦。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
/// <remarks>
|
||||||
|
/// <para>
|
||||||
|
/// 该输出器在后台线程中顺序消费日志条目,因此调用方不会因为文件 IO 或其他慢速输出目标而阻塞。
|
||||||
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// 内部输出器抛出的异常不会重新抛回调用线程;如需观察后台处理失败,请在构造函数中提供
|
||||||
|
/// <c>processingErrorHandler</c> 回调。
|
||||||
|
/// </para>
|
||||||
|
/// </remarks>
|
||||||
|
public sealed class AsyncLogAppender : ILogAppender
|
||||||
{
|
{
|
||||||
private readonly Channel<LogEntry> _channel;
|
private readonly Channel<LogEntry> _channel;
|
||||||
private readonly CancellationTokenSource _cts;
|
private readonly CancellationTokenSource _cts;
|
||||||
private readonly SemaphoreSlim _flushSemaphore = new(0, 1);
|
private readonly SemaphoreSlim _flushSemaphore = new(0, 1);
|
||||||
private readonly ILogAppender _innerAppender;
|
private readonly ILogAppender _innerAppender;
|
||||||
|
private readonly Action<Exception>? _processingErrorHandler;
|
||||||
private readonly Task _processingTask;
|
private readonly Task _processingTask;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
private volatile bool _flushRequested;
|
private volatile bool _flushRequested;
|
||||||
@ -21,9 +30,17 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="innerAppender">内部日志输出器</param>
|
/// <param name="innerAppender">内部日志输出器</param>
|
||||||
/// <param name="bufferSize">缓冲区大小(默认 10000)</param>
|
/// <param name="bufferSize">缓冲区大小(默认 10000)</param>
|
||||||
public AsyncLogAppender(ILogAppender innerAppender, int bufferSize = 10000)
|
/// <param name="processingErrorHandler">
|
||||||
|
/// 后台处理日志时的错误回调。
|
||||||
|
/// 默认值为 <see langword="null" />,表示吞掉内部异常以避免污染宿主标准错误输出。
|
||||||
|
/// </param>
|
||||||
|
public AsyncLogAppender(
|
||||||
|
ILogAppender innerAppender,
|
||||||
|
int bufferSize = 10000,
|
||||||
|
Action<Exception>? processingErrorHandler = null)
|
||||||
{
|
{
|
||||||
_innerAppender = innerAppender ?? throw new ArgumentNullException(nameof(innerAppender));
|
_innerAppender = innerAppender ?? throw new ArgumentNullException(nameof(innerAppender));
|
||||||
|
_processingErrorHandler = processingErrorHandler;
|
||||||
|
|
||||||
if (bufferSize <= 0)
|
if (bufferSize <= 0)
|
||||||
throw new ArgumentException("Buffer size must be greater than 0.", nameof(bufferSize));
|
throw new ArgumentException("Buffer size must be greater than 0.", nameof(bufferSize));
|
||||||
@ -138,7 +155,8 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 后台处理日志的异步方法
|
/// 后台处理日志的异步方法。
|
||||||
|
/// 该循环必须始终保持存活,因此所有内部异常都通过回调上报并被吞掉。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private async Task ProcessLogsAsync(CancellationToken cancellationToken)
|
private async Task ProcessLogsAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
@ -152,8 +170,8 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 记录内部错误到控制台(避免递归)
|
// 后台消费失败只通过显式回调暴露,避免测试宿主将 stderr 误判为测试告警。
|
||||||
await Console.Error.WriteLineAsync($"[AsyncLogAppender] Error processing log entry: {ex.Message}");
|
ReportProcessingError(ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查是否有刷新请求且通道已空
|
// 检查是否有刷新请求且通道已空
|
||||||
@ -175,7 +193,7 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
await Console.Error.WriteLineAsync($"[AsyncLogAppender] Fatal error in processing task: {ex}");
|
ReportProcessingError(ex);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
@ -184,10 +202,37 @@ public sealed class AsyncLogAppender : ILogAppender, IDisposable
|
|||||||
{
|
{
|
||||||
_innerAppender.Flush();
|
_innerAppender.Flush();
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// 忽略刷新错误
|
ReportProcessingError(ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 上报后台处理异常,同时隔离观察者自身抛出的错误,避免终止处理循环。
|
||||||
|
/// 取消相关异常表示关闭流程中的预期控制流,不应被视为后台处理失败。
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="exception">后台处理中捕获到的异常。</param>
|
||||||
|
private void ReportProcessingError(Exception exception)
|
||||||
|
{
|
||||||
|
if (exception is OperationCanceledException)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_processingErrorHandler is null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
_processingErrorHandler(exception);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// 错误观察者只用于诊断,绝不能反向影响日志处理线程的生命周期。
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -80,7 +80,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
|||||||
// 触发事件
|
// 触发事件
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
OnPauseStateChanged?.Invoke(group, false);
|
RaisePauseStateChanged(group, false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -97,7 +97,7 @@ public class PauseStackManager : AbstractContextUtility, IPauseStackManager, IAs
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 暂停状态变化事件,当暂停状态发生改变时触发。
|
/// 暂停状态变化事件,当暂停状态发生改变时触发。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<PauseGroup, bool>? OnPauseStateChanged;
|
public event EventHandler<PauseStateChangedEventArgs>? OnPauseStateChanged;
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// <summary>
|
||||||
|
|||||||
@ -21,12 +21,6 @@ namespace GFramework.Game.Abstractions.Scene;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public interface ISceneBehavior : IRoute
|
public interface ISceneBehavior : IRoute
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 获取场景的唯一标识符。
|
|
||||||
/// 用于区分不同的场景实例。
|
|
||||||
/// </summary>
|
|
||||||
string Key { get; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取场景的原始对象。
|
/// 获取场景的原始对象。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -31,8 +31,9 @@ public interface ISceneRouteGuard : IRouteGuard<ISceneBehavior>
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 异步检查是否允许离开指定场景。
|
/// 异步检查是否允许离开指定场景。
|
||||||
|
/// 该成员显式细化了通用路由守卫的离开检查,使场景守卫在 API 文档中保持场景语义。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sceneKey">当前场景的唯一标识符。</param>
|
/// <param name="sceneKey">当前场景的唯一标识符。</param>
|
||||||
/// <returns>如果允许离开则返回 true,否则返回 false。</returns>
|
/// <returns>如果允许离开则返回 true,否则返回 false。</returns>
|
||||||
ValueTask<bool> CanLeaveAsync(string sceneKey);
|
new ValueTask<bool> CanLeaveAsync(string sceneKey);
|
||||||
}
|
}
|
||||||
@ -46,14 +46,6 @@ public interface IUiPageBehavior : IRoute
|
|||||||
/// <returns>页面视图实例。</returns>
|
/// <returns>页面视图实例。</returns>
|
||||||
object View { get; }
|
object View { get; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 获取键值
|
|
||||||
/// </summary>
|
|
||||||
/// <value>返回当前对象的键标识符</value>
|
|
||||||
string Key { get; }
|
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取页面是否处于活动状态
|
/// 获取页面是否处于活动状态
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -17,9 +17,10 @@ public interface IUiRouteGuard : IRouteGuard<IUiPageBehavior>
|
|||||||
ValueTask<bool> CanEnterAsync(string uiKey, IUiPageEnterParam? param);
|
ValueTask<bool> CanEnterAsync(string uiKey, IUiPageEnterParam? param);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 离开UI前的检查
|
/// 离开UI前的检查。
|
||||||
|
/// 该成员显式细化了通用路由守卫的离开检查,使 UI 守卫在 API 文档中保持 UI 语义。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="uiKey">当前UI标识符</param>
|
/// <param name="uiKey">当前UI标识符</param>
|
||||||
/// <returns>true表示允许离开,false表示拦截</returns>
|
/// <returns>true表示允许离开,false表示拦截</returns>
|
||||||
ValueTask<bool> CanLeaveAsync(string uiKey);
|
new ValueTask<bool> CanLeaveAsync(string uiKey);
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
|
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
|
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
|
||||||
<!-- 这是 Analyzer,不是运行时库 -->
|
<!-- 这是 Analyzer,不是运行时库 -->
|
||||||
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
||||||
|
|||||||
@ -6,6 +6,9 @@
|
|||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
|
<!-- Godot.SourceGenerators expects this property from Godot.NET.Sdk.
|
||||||
|
Provide a safe default so source generators can run in plain SDK-style builds as well. -->
|
||||||
|
<GodotProjectDir Condition="'$(GodotProjectDir)' == ''">$(MSBuildProjectDirectory)</GodotProjectDir>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
namespace GFramework.SourceGenerators.Abstractions.Bases;
|
namespace GFramework.SourceGenerators.Abstractions.Bases;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 标记类的优先级,自动生成 <see cref="GFramework.Core.Abstractions.Bases.IPrioritized"/> 接口实现
|
/// 标记类的优先级,自动生成 <c>GFramework.Core.Abstractions.Bases.IPrioritized</c> 接口实现。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// 使用此特性可以避免手动实现 IPrioritized 接口。
|
/// 使用此特性可以避免手动实现 IPrioritized 接口。
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
|
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@ -14,9 +14,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Using Include="GFramework.SourceGenerators.Abstractions"/>
|
<Using Include="GFramework.SourceGenerators.Abstractions"/>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
|
||||||
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj" PrivateAssets="all"/>
|
|
||||||
</ItemGroup>
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Update="Meziantou.Analyzer" Version="3.0.25">
|
<PackageReference Update="Meziantou.Analyzer" Version="3.0.25">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
<Project>
|
<Project>
|
||||||
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
|
<!-- import parent: https://docs.microsoft.com/en-us/visualstudio/msbuild/customize-your-build -->
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
|
||||||
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
<EmbedUntrackedSources>true</EmbedUntrackedSources>
|
||||||
<!--
|
<!--
|
||||||
|
|||||||
@ -56,7 +56,7 @@ public sealed class PriorityGenerator : MetadataAttributeClassGeneratorBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 3. 必须是 partial
|
// 3. 必须是 partial
|
||||||
if (syntax.Modifiers.All(m => m.Kind() != SyntaxKind.PartialKeyword))
|
if (syntax.Modifiers.All(m => !m.IsKind(SyntaxKind.PartialKeyword)))
|
||||||
{
|
{
|
||||||
context.ReportDiagnostic(Diagnostic.Create(
|
context.ReportDiagnostic(Diagnostic.Create(
|
||||||
PriorityDiagnostic.MustBePartial,
|
PriorityDiagnostic.MustBePartial,
|
||||||
|
|||||||
@ -19,7 +19,7 @@ internal static class PriorityDiagnostic
|
|||||||
category: Category,
|
category: Category,
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
description: "Priority 特性设计用于类级别的优先级标记,不支持其他类型。"
|
description: "Priority 特性设计用于类级别的优先级标记,不支持其他类型."
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -32,7 +32,7 @@ internal static class PriorityDiagnostic
|
|||||||
category: Category,
|
category: Category,
|
||||||
defaultSeverity: DiagnosticSeverity.Warning,
|
defaultSeverity: DiagnosticSeverity.Warning,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
description: "当类已经手动实现 IPrioritized 接口时,源生成器将跳过代码生成以避免冲突。"
|
description: "当类已经手动实现 IPrioritized 接口时,源生成器将跳过代码生成以避免冲突."
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -45,7 +45,7 @@ internal static class PriorityDiagnostic
|
|||||||
category: Category,
|
category: Category,
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
description: "源生成器需要在 partial 类中生成 IPrioritized 接口实现。"
|
description: "源生成器需要在 partial 类中生成 IPrioritized 接口实现."
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -58,7 +58,7 @@ internal static class PriorityDiagnostic
|
|||||||
category: Category,
|
category: Category,
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
description: "Priority 特性必须提供一个有效的整数值。"
|
description: "Priority 特性必须提供一个有效的整数值."
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -71,7 +71,7 @@ internal static class PriorityDiagnostic
|
|||||||
category: Category,
|
category: Category,
|
||||||
defaultSeverity: DiagnosticSeverity.Error,
|
defaultSeverity: DiagnosticSeverity.Error,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
description: "Priority 特性仅支持顶层类,不支持嵌套类。请将嵌套类移至命名空间级别。"
|
description: "Priority 特性仅支持顶层类,不支持嵌套类.请将嵌套类移至命名空间级别."
|
||||||
);
|
);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -84,6 +84,6 @@ internal static class PriorityDiagnostic
|
|||||||
category: "GFramework.Usage",
|
category: "GFramework.Usage",
|
||||||
defaultSeverity: DiagnosticSeverity.Info,
|
defaultSeverity: DiagnosticSeverity.Info,
|
||||||
isEnabledByDefault: true,
|
isEnabledByDefault: true,
|
||||||
description: "当获取实现了 IPrioritized 接口的服务时,应使用 GetAllByPriority 方法以确保按优先级排序。"
|
description: "当获取实现了 IPrioritized 接口的服务时,应使用 GetAllByPriority 方法以确保按优先级排序."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
|
<PackageId>GeWuYou.$(AssemblyName)</PackageId>
|
||||||
<TargetFramework>netstandard2.1</TargetFramework>
|
<TargetFramework>netstandard2.0</TargetFramework>
|
||||||
|
|
||||||
<!-- 这是 Analyzer,不是运行时库 -->
|
<!-- 这是 Analyzer,不是运行时库 -->
|
||||||
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
<IsRoslynAnalyzer>true</IsRoslynAnalyzer>
|
||||||
@ -30,7 +30,6 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj" PrivateAssets="all"/>
|
<ProjectReference Include="..\$(AssemblyName).Abstractions\$(AssemblyName).Abstractions.csproj" PrivateAssets="all"/>
|
||||||
<ProjectReference Include="..\$(AssemblyName).Common\$(AssemblyName).Common.csproj" PrivateAssets="all"/>
|
<ProjectReference Include="..\$(AssemblyName).Common\$(AssemblyName).Common.csproj" PrivateAssets="all"/>
|
||||||
<ProjectReference Include="..\GFramework.Core.Abstractions\GFramework.Core.Abstractions.csproj" PrivateAssets="all"/>
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<!-- ★关键:只把 Generator DLL 放进 analyzers -->
|
<!-- ★关键:只把 Generator DLL 放进 analyzers -->
|
||||||
|
|||||||
@ -16,11 +16,16 @@
|
|||||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||||
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
<TargetFrameworks>net8.0;net9.0;net10.0</TargetFrameworks>
|
||||||
<IncludeBuildOutput>false</IncludeBuildOutput>
|
<IncludeBuildOutput>false</IncludeBuildOutput>
|
||||||
|
<!-- This package is a pure meta-package that only aggregates dependencies. -->
|
||||||
|
<NoPackageAnalysis>false</NoPackageAnalysis>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<!-- 排除不需要参与打包/编译的目录 -->
|
<!-- 排除不需要参与打包/编译的目录 -->
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="README.md" Pack="true" PackagePath=""/>
|
<None Include="README.md" Pack="true" PackagePath=""/>
|
||||||
|
<None Include="packaging/_._" Pack="true" PackagePath="lib/net8.0/_._"/>
|
||||||
|
<None Include="packaging/_._" Pack="true" PackagePath="lib/net9.0/_._"/>
|
||||||
|
<None Include="packaging/_._" Pack="true" PackagePath="lib/net10.0/_._"/>
|
||||||
<None Remove="GFramework.Core\**"/>
|
<None Remove="GFramework.Core\**"/>
|
||||||
<None Remove="GFramework.Game\**"/>
|
<None Remove="GFramework.Game\**"/>
|
||||||
<None Remove="GFramework.Godot\**"/>
|
<None Remove="GFramework.Godot\**"/>
|
||||||
|
|||||||
@ -100,8 +100,8 @@ void ClearAll();
|
|||||||
void RegisterHandler(IPauseHandler handler);
|
void RegisterHandler(IPauseHandler handler);
|
||||||
void UnregisterHandler(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;
|
_pauseManager.OnPauseStateChanged += OnPauseStateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
|
private void OnPauseStateChanged(object? sender, PauseStateChangedEventArgs e)
|
||||||
{
|
{
|
||||||
Console.WriteLine($"暂停状态变化: 组={group}, 暂停={isPaused}");
|
Console.WriteLine($"暂停状态变化: 组={e.Group}, 暂停={e.IsPaused}");
|
||||||
|
|
||||||
if (group == PauseGroup.Global)
|
if (e.Group == PauseGroup.Global)
|
||||||
{
|
{
|
||||||
if (isPaused)
|
if (e.IsPaused)
|
||||||
{
|
{
|
||||||
ShowPauseIndicator();
|
ShowPauseIndicator();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HidePauseIndicator();
|
HidePauseIndicator();
|
||||||
@ -705,7 +705,7 @@ public partial class ProperCleanup : IController
|
|||||||
_pauseManager.OnPauseStateChanged -= OnPauseChanged;
|
_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: 监听暂停事件
|
// 方案 2: 监听暂停事件
|
||||||
var pauseManager = this.GetUtility<IPauseStackManager>();
|
var pauseManager = this.GetUtility<IPauseStackManager>();
|
||||||
pauseManager.OnPauseStateChanged += (group, isPaused) =>
|
pauseManager.OnPauseStateChanged += (_, e) =>
|
||||||
{
|
{
|
||||||
if (group == PauseGroup.Global)
|
if (e.Group == PauseGroup.Global)
|
||||||
{
|
{
|
||||||
Visible = isPaused;
|
Visible = e.IsPaused;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -887,15 +887,15 @@ public class PauseEventBridge : AbstractSystem
|
|||||||
{
|
{
|
||||||
var pauseManager = this.GetUtility<IPauseStackManager>();
|
var pauseManager = this.GetUtility<IPauseStackManager>();
|
||||||
|
|
||||||
pauseManager.OnPauseStateChanged += (group, isPaused) =>
|
pauseManager.OnPauseStateChanged += (_, e) =>
|
||||||
{
|
{
|
||||||
// 发送暂停事件
|
// 发送暂停事件
|
||||||
this.SendEvent(new GamePausedEvent
|
this.SendEvent(new GamePausedEvent
|
||||||
{
|
{
|
||||||
Group = group,
|
Group = e.Group,
|
||||||
IsPaused = isPaused
|
IsPaused = e.IsPaused
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,7 +44,7 @@ public interface IPauseStackManager : IContextUtility
|
|||||||
int GetPauseDepth(PauseGroup group = PauseGroup.Global);
|
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;
|
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 ? "游戏已暂停" : "游戏运行中";
|
Text = e.IsPaused ? "游戏已暂停" : "游戏运行中";
|
||||||
Visible = isPaused;
|
Visible = e.IsPaused;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -502,16 +502,16 @@ public partial class PauseDebugger : Node
|
|||||||
pauseManager.OnPauseStateChanged += OnPauseStateChanged;
|
pauseManager.OnPauseStateChanged += OnPauseStateChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnPauseStateChanged(PauseGroup group, bool isPaused)
|
private void OnPauseStateChanged(object? sender, PauseStateChangedEventArgs e)
|
||||||
{
|
{
|
||||||
var pauseManager = this.GetUtility<IPauseStackManager>();
|
var pauseManager = this.GetUtility<IPauseStackManager>();
|
||||||
|
|
||||||
GD.Print($"=== 暂停状态变化 ===");
|
GD.Print($"=== 暂停状态变化 ===");
|
||||||
GD.Print($"组: {group}");
|
GD.Print($"组: {e.Group}");
|
||||||
GD.Print($"状态: {(isPaused ? "暂停" : "恢复")}");
|
GD.Print($"状态: {(e.IsPaused ? "暂停" : "恢复")}");
|
||||||
GD.Print($"深度: {pauseManager.GetPauseDepth(group)}");
|
GD.Print($"深度: {pauseManager.GetPauseDepth(e.Group)}");
|
||||||
|
|
||||||
var reasons = pauseManager.GetPauseReasons(group);
|
var reasons = pauseManager.GetPauseReasons(e.Group);
|
||||||
if (reasons.Count > 0)
|
if (reasons.Count > 0)
|
||||||
{
|
{
|
||||||
GD.Print($"原因:");
|
GD.Print($"原因:");
|
||||||
@ -609,9 +609,9 @@ public partial class PauseDebugger : Node
|
|||||||
|
|
||||||
7. **使用事件监听暂停状态**:实现响应式 UI
|
7. **使用事件监听暂停状态**:实现响应式 UI
|
||||||
```csharp
|
```csharp
|
||||||
pauseManager.OnPauseStateChanged += (group, isPaused) =>
|
pauseManager.OnPauseStateChanged += (_, e) =>
|
||||||
{
|
{
|
||||||
UpdateUI(isPaused);
|
UpdateUI(e.IsPaused);
|
||||||
};
|
};
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
1
packaging/_._
Normal file
1
packaging/_._
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user