mirror of
https://github.com/GeWuYou/GFramework.git
synced 2026-05-14 14:58:58 +08:00
Compare commits
No commits in common. "d21370787bee380f7874fdd1eb0c8758d9b72d2e" and "6cac882fb43a3fd2f897fcd22ac14c60906729d7" have entirely different histories.
d21370787b
...
6cac882fb4
5
.github/workflows/ci.yml
vendored
5
.github/workflows/ci.yml
vendored
@ -156,11 +156,6 @@ jobs:
|
|||||||
--logger "trx;LogFileName=ecs-arch-$RANDOM.trx" \
|
--logger "trx;LogFileName=ecs-arch-$RANDOM.trx" \
|
||||||
--results-directory TestResults &
|
--results-directory TestResults &
|
||||||
|
|
||||||
dotnet test GFramework.Godot.Tests \
|
|
||||||
-c Release \
|
|
||||||
--no-build \
|
|
||||||
--logger "trx;LogFileName=godot-$RANDOM.trx" \
|
|
||||||
--results-directory TestResults &
|
|
||||||
# 等待所有后台测试完成
|
# 等待所有后台测试完成
|
||||||
wait
|
wait
|
||||||
|
|
||||||
|
|||||||
@ -171,63 +171,4 @@ public sealed class CoroutineSchedulerAdvancedTests
|
|||||||
|
|
||||||
Assert.That(status, Is.EqualTo(CoroutineCompletionStatus.Faulted));
|
Assert.That(status, Is.EqualTo(CoroutineCompletionStatus.Faulted));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证完成状态缓存有固定上限,避免无限增长。
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void CompletionStatusHistory_Should_Be_Bounded()
|
|
||||||
{
|
|
||||||
var timeSource = new FakeTimeSource();
|
|
||||||
var scheduler = new CoroutineScheduler(timeSource);
|
|
||||||
var handles = new List<CoroutineHandle>();
|
|
||||||
|
|
||||||
IEnumerator<IYieldInstruction> ImmediateCoroutine()
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (var i = 0; i < 1100; i++)
|
|
||||||
{
|
|
||||||
handles.Add(scheduler.Run(ImmediateCoroutine()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Assert.That(scheduler.TryGetCompletionStatus(handles[0], out _), Is.False);
|
|
||||||
Assert.That(scheduler.TryGetCompletionStatus(handles[^1], out var latestStatus), Is.True);
|
|
||||||
Assert.That(latestStatus, Is.EqualTo(CoroutineCompletionStatus.Completed));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证作为首个等待指令的 WaitForCoroutine 会立即启动子协程,并沿用父协程取消令牌。
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public async Task WaitForCoroutine_Should_Start_Child_During_Prewarm_And_Propagate_Cancellation()
|
|
||||||
{
|
|
||||||
var timeSource = new FakeTimeSource();
|
|
||||||
var scheduler = new CoroutineScheduler(timeSource);
|
|
||||||
using var cancellationTokenSource = new CancellationTokenSource();
|
|
||||||
|
|
||||||
IEnumerator<IYieldInstruction> ChildCoroutine()
|
|
||||||
{
|
|
||||||
yield return new Delay(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerator<IYieldInstruction> ParentCoroutine()
|
|
||||||
{
|
|
||||||
yield return new WaitForCoroutine(ChildCoroutine());
|
|
||||||
}
|
|
||||||
|
|
||||||
var handle = scheduler.Run(ParentCoroutine(), cancellationToken: cancellationTokenSource.Token);
|
|
||||||
|
|
||||||
Assert.That(scheduler.ActiveCoroutineCount, Is.EqualTo(2));
|
|
||||||
|
|
||||||
cancellationTokenSource.Cancel();
|
|
||||||
timeSource.Advance(0.1);
|
|
||||||
scheduler.Update();
|
|
||||||
|
|
||||||
var status = await scheduler.WaitForCompletionAsync(handle);
|
|
||||||
|
|
||||||
Assert.That(status, Is.EqualTo(CoroutineCompletionStatus.Cancelled));
|
|
||||||
Assert.That(scheduler.ActiveCoroutineCount, Is.EqualTo(0));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -34,13 +34,10 @@ public sealed class CoroutineScheduler(
|
|||||||
ITimeSource? realtimeTimeSource = null,
|
ITimeSource? realtimeTimeSource = null,
|
||||||
CoroutineExecutionStage executionStage = CoroutineExecutionStage.Update)
|
CoroutineExecutionStage executionStage = CoroutineExecutionStage.Update)
|
||||||
{
|
{
|
||||||
private const int CompletionStatusHistoryLimit = 1024;
|
|
||||||
|
|
||||||
private readonly Dictionary<CoroutineHandle, TaskCompletionSource<CoroutineCompletionStatus>> _completionSources =
|
private readonly Dictionary<CoroutineHandle, TaskCompletionSource<CoroutineCompletionStatus>> _completionSources =
|
||||||
new();
|
new();
|
||||||
|
|
||||||
private readonly Dictionary<CoroutineHandle, CoroutineCompletionStatus> _completionStatuses = new();
|
private readonly Dictionary<CoroutineHandle, CoroutineCompletionStatus> _completionStatuses = new();
|
||||||
private readonly Queue<CoroutineHandle> _completionStatusOrder = new();
|
|
||||||
private readonly Dictionary<string, HashSet<CoroutineHandle>> _grouped = new();
|
private readonly Dictionary<string, HashSet<CoroutineHandle>> _grouped = new();
|
||||||
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CoroutineScheduler));
|
private readonly ILogger _logger = LoggerFactoryResolver.Provider.CreateLogger(nameof(CoroutineScheduler));
|
||||||
private readonly Dictionary<CoroutineHandle, CoroutineMetadata> _metadata = new();
|
private readonly Dictionary<CoroutineHandle, CoroutineMetadata> _metadata = new();
|
||||||
@ -221,7 +218,6 @@ public sealed class CoroutineScheduler(
|
|||||||
|
|
||||||
var slot = new CoroutineSlot
|
var slot = new CoroutineSlot
|
||||||
{
|
{
|
||||||
CancellationToken = cancellationToken,
|
|
||||||
Enumerator = coroutine,
|
Enumerator = coroutine,
|
||||||
State = CoroutineState.Running,
|
State = CoroutineState.Running,
|
||||||
Handle = handle,
|
Handle = handle,
|
||||||
@ -390,14 +386,7 @@ public sealed class CoroutineScheduler(
|
|||||||
{
|
{
|
||||||
case WaitForCoroutine waitForCoroutine:
|
case WaitForCoroutine waitForCoroutine:
|
||||||
{
|
{
|
||||||
var targetHandle = Run(waitForCoroutine.Coroutine, cancellationToken: slot.CancellationToken);
|
var targetHandle = Run(waitForCoroutine.Coroutine);
|
||||||
if (!targetHandle.IsValid)
|
|
||||||
{
|
|
||||||
waitForCoroutine.Complete();
|
|
||||||
slot.Waiting = null;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
slot.Waiting = waitForCoroutine;
|
slot.Waiting = waitForCoroutine;
|
||||||
WaitForCoroutine(slot.Handle, targetHandle);
|
WaitForCoroutine(slot.Handle, targetHandle);
|
||||||
break;
|
break;
|
||||||
@ -607,8 +596,6 @@ public sealed class CoroutineScheduler(
|
|||||||
_tagged.Clear();
|
_tagged.Clear();
|
||||||
_grouped.Clear();
|
_grouped.Clear();
|
||||||
_waiting.Clear();
|
_waiting.Clear();
|
||||||
_completionStatuses.Clear();
|
|
||||||
_completionStatusOrder.Clear();
|
|
||||||
|
|
||||||
_nextSlot = 0;
|
_nextSlot = 0;
|
||||||
ActiveCoroutineCount = 0;
|
ActiveCoroutineCount = 0;
|
||||||
@ -642,7 +629,7 @@ public sealed class CoroutineScheduler(
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
HandleYieldInstruction(slot, slot.Enumerator.Current);
|
slot.Waiting = slot.Enumerator.Current;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@ -725,7 +712,7 @@ public sealed class CoroutineScheduler(
|
|||||||
source.TrySetResult(completionStatus);
|
source.TrySetResult(completionStatus);
|
||||||
}
|
}
|
||||||
|
|
||||||
RecordCompletionStatus(handle, completionStatus);
|
_completionStatuses[handle] = completionStatus;
|
||||||
OnCoroutineFinished?.Invoke(handle, completionStatus, exception);
|
OnCoroutineFinished?.Invoke(handle, completionStatus, exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -905,23 +892,6 @@ public sealed class CoroutineScheduler(
|
|||||||
_statistics.PausedCount = _pausedCount;
|
_statistics.PausedCount = _pausedCount;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 记录协程最终状态,并对历史缓存施加固定上限,避免完成状态字典无限增长。
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="handle">已结束的协程句柄。</param>
|
|
||||||
/// <param name="completionStatus">协程最终状态。</param>
|
|
||||||
private void RecordCompletionStatus(CoroutineHandle handle, CoroutineCompletionStatus completionStatus)
|
|
||||||
{
|
|
||||||
_completionStatuses[handle] = completionStatus;
|
|
||||||
_completionStatusOrder.Enqueue(handle);
|
|
||||||
|
|
||||||
while (_completionStatusOrder.Count > CompletionStatusHistoryLimit)
|
|
||||||
{
|
|
||||||
var expiredHandle = _completionStatusOrder.Dequeue();
|
|
||||||
_completionStatuses.Remove(expiredHandle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 为协程添加标签。
|
/// 为协程添加标签。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -13,12 +13,6 @@ internal sealed class CoroutineSlot
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public CancellationTokenRegistration CancellationRegistration;
|
public CancellationTokenRegistration CancellationRegistration;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 创建该协程时传入的取消令牌。
|
|
||||||
/// 当协程启动子协程时,会把同一个取消令牌继续传递下去,以保持父子协程的取消语义一致。
|
|
||||||
/// </summary>
|
|
||||||
public CancellationToken CancellationToken;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 协程枚举器,包含协程的执行逻辑
|
/// 协程枚举器,包含协程的执行逻辑
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -49,25 +49,4 @@ public sealed class GodotTimeSourceTests
|
|||||||
Assert.That(timeSource.DeltaTime, Is.EqualTo(0.75).Within(0.0001));
|
Assert.That(timeSource.DeltaTime, Is.EqualTo(0.75).Within(0.0001));
|
||||||
Assert.That(timeSource.CurrentTime, Is.EqualTo(2.0).Within(0.0001));
|
Assert.That(timeSource.CurrentTime, Is.EqualTo(2.0).Within(0.0001));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 验证绝对时间源在回拨时仍保持单调,不会把 CurrentTime 拉回去。
|
|
||||||
/// </summary>
|
|
||||||
[Test]
|
|
||||||
public void Update_Should_Keep_Absolute_Time_Monotonic_When_Provider_Goes_Backwards()
|
|
||||||
{
|
|
||||||
var values = new Queue<double>([5.0, 4.0, 6.5]);
|
|
||||||
var timeSource = new GodotTimeSource(() => values.Dequeue(), useAbsoluteTime: true);
|
|
||||||
|
|
||||||
timeSource.Update();
|
|
||||||
timeSource.Update();
|
|
||||||
|
|
||||||
Assert.That(timeSource.DeltaTime, Is.EqualTo(0).Within(0.0001));
|
|
||||||
Assert.That(timeSource.CurrentTime, Is.EqualTo(5.0).Within(0.0001));
|
|
||||||
|
|
||||||
timeSource.Update();
|
|
||||||
|
|
||||||
Assert.That(timeSource.DeltaTime, Is.EqualTo(1.5).Within(0.0001));
|
|
||||||
Assert.That(timeSource.CurrentTime, Is.EqualTo(6.5).Within(0.0001));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
using GFramework.Core.Abstractions.Coroutine;
|
using GFramework.Core.Abstractions.Coroutine;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
namespace GFramework.Godot.Coroutine;
|
namespace GFramework.Godot.Coroutine;
|
||||||
|
|
||||||
@ -46,11 +47,9 @@ public sealed class GodotTimeSource(Func<double> timeProvider, bool useAbsoluteT
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对绝对时间源做单调钳制,避免 provider 回拨后把 CurrentTime 也拉回去。
|
DeltaTime = Math.Max(0, value - _lastAbsoluteTime);
|
||||||
var nextTime = Math.Max(value, _lastAbsoluteTime);
|
_lastAbsoluteTime = value;
|
||||||
DeltaTime = nextTime - _lastAbsoluteTime;
|
CurrentTime = value;
|
||||||
_lastAbsoluteTime = nextTime;
|
|
||||||
CurrentTime = nextTime;
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ using GFramework.Core.Abstractions.Coroutine;
|
|||||||
using GFramework.Core.Coroutine;
|
using GFramework.Core.Coroutine;
|
||||||
using GFramework.Core.Coroutine.Instructions;
|
using GFramework.Core.Coroutine.Instructions;
|
||||||
using GFramework.Godot.Extensions;
|
using GFramework.Godot.Extensions;
|
||||||
|
using Godot;
|
||||||
|
|
||||||
namespace GFramework.Godot.Coroutine;
|
namespace GFramework.Godot.Coroutine;
|
||||||
|
|
||||||
@ -469,11 +470,6 @@ public partial class Timing : Node
|
|||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!GetScheduler(segment).IsCoroutineAlive(handle))
|
|
||||||
{
|
|
||||||
return handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
RegisterOwnedCoroutine(owner, handle);
|
RegisterOwnedCoroutine(owner, handle);
|
||||||
return handle;
|
return handle;
|
||||||
}
|
}
|
||||||
@ -519,13 +515,7 @@ public partial class Timing : Node
|
|||||||
/// <returns>被终止的协程数量。</returns>
|
/// <returns>被终止的协程数量。</returns>
|
||||||
public static int KillCoroutines(Node owner)
|
public static int KillCoroutines(Node owner)
|
||||||
{
|
{
|
||||||
var count = 0;
|
return Instance.KillOwnedCoroutinesOnInstance(owner);
|
||||||
foreach (var timing in EnumerateActiveInstances())
|
|
||||||
{
|
|
||||||
count += timing.KillOwnedCoroutinesOnInstance(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -572,13 +562,7 @@ public partial class Timing : Node
|
|||||||
/// <returns>该节点当前归属的活跃协程数量。</returns>
|
/// <returns>该节点当前归属的活跃协程数量。</returns>
|
||||||
public static int GetOwnedCoroutineCount(Node owner)
|
public static int GetOwnedCoroutineCount(Node owner)
|
||||||
{
|
{
|
||||||
var count = 0;
|
return Instance.GetOwnedCoroutineCountOnInstance(owner);
|
||||||
foreach (var timing in EnumerateActiveInstances())
|
|
||||||
{
|
|
||||||
count += timing.GetOwnedCoroutineCountOnInstance(owner);
|
|
||||||
}
|
|
||||||
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -699,15 +683,6 @@ public partial class Timing : Node
|
|||||||
return id < ActiveInstances.Length ? ActiveInstances[id] : null;
|
return id < ActiveInstances.Length ? ActiveInstances[id] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 枚举所有当前已注册的 Timing 实例。
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>活跃 Timing 实例序列。</returns>
|
|
||||||
private static IEnumerable<Timing> EnumerateActiveInstances()
|
|
||||||
{
|
|
||||||
return ActiveInstances.Where(static timing => timing is not null).Select(static timing => timing!);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查节点是否处于有效状态。
|
/// 检查节点是否处于有效状态。
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@ -15,5 +15,4 @@ 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 Godot;
|
|
||||||
Loading…
x
Reference in New Issue
Block a user