From 61fbce14a247b8ddf3da1b1eb838ace9a85804fc Mon Sep 17 00:00:00 2001 From: Youmu Liu <105042599+Nitload-NSI@users.noreply.github.com> Date: Fri, 21 Nov 2025 18:11:52 +0800 Subject: [PATCH] add cancellable time span await --- StgSharp.Common/HighPerformance/M64.cs | 27 ++-------- .../Timing/TimeSpanProvider.Awaiting.cs | 51 ++++++++++++++++++- StgSharp.Common/Timing/TimeSpanProvider.cs | 8 +++ 3 files changed, 63 insertions(+), 23 deletions(-) diff --git a/StgSharp.Common/HighPerformance/M64.cs b/StgSharp.Common/HighPerformance/M64.cs index 13b4a67e..ceb59890 100644 --- a/StgSharp.Common/HighPerformance/M64.cs +++ b/StgSharp.Common/HighPerformance/M64.cs @@ -43,15 +43,16 @@ namespace StgSharp.HighPerformance public M64() { Unsafe.SkipInit(out this); - value = 0; + Unsafe.SkipInit(out vec); + Unsafe.SkipInit(out value); } - public ref T AsRef() where T: unmanaged,INumber + public ref T AsRef() where T : unmanaged,INumber { return ref Unsafe.As(ref buffer[0]); } - public void BroadCastFrom(T value) where T: unmanaged, INumber + public void BroadCastFrom(T value) where T : unmanaged, INumber { vec = Vector64.Create(value).AsSingle(); } @@ -67,7 +68,7 @@ namespace StgSharp.HighPerformance } [MethodImpl(MethodImplOptions.AggressiveInlining)] - public ref T Member(int index) where T: unmanaged, INumber + public ref T Member(int index) where T : unmanaged, INumber { return ref Unsafe.As(ref buffer[index * sizeof(T)]); } @@ -77,28 +78,10 @@ namespace StgSharp.HighPerformance return !(left == right); } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static M64 operator <<(M64 m, int shift) - { - return new M64 - { - value = m.value << shift - }; - } - public static bool operator ==(M64 left, M64 right) { return left.value == right.value; } - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static M64 operator >>(M64 m, int shift) - { - return new M64 - { - value = m.value >> shift - }; - } - } } diff --git a/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs b/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs index 1763ddd1..128c6ab1 100644 --- a/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs +++ b/StgSharp.Common/Timing/TimeSpanProvider.Awaiting.cs @@ -85,6 +85,12 @@ namespace StgSharp.Timing public void Dispose() { + // mark provider as ended so external users can observe terminal state + try + { + Volatile.Write(ref _endedFlag, 1); + } + catch { } s_unusedID.Push(TokenID); _event.Dispose(); GC.SuppressFinalize(this); @@ -147,6 +153,46 @@ namespace StgSharp.Timing } } + /// + /// Wait for the next timespan tick, but honor the provided cancellation token. Throws + /// OperationCanceledException when token is cancelled. If the provider is disposed during + /// wait an OperationCanceledException is thrown to indicate end. + /// + public Task WaitNextSpanAsync(CancellationToken ct) + { + try + { + // Block the current thread until the event is signaled or cancellation requested. + _event.Wait(ct); + } + catch (ObjectDisposedException) + { + // provider disposed while waiting - treat as cancellation/ended + throw new OperationCanceledException(ct); + } + + // consume one pending span (same logic as non-cancellable variant) + while (true) + { + int cur = Volatile.Read(ref _pendingSpans); + if (cur == 0) + { + break; + } + + if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur) + { + if (cur - 1 == 0) { + _event.Reset(); + } + + break; + } + } + + return Task.CompletedTask; + } + internal void MissRefresh() => _missHandler?.Invoke(this, EventArgs.Empty); // Legacy compatibility. /// @@ -163,13 +209,16 @@ namespace StgSharp.Timing { if (_sequence.EndsAt(currentTick)) { + // mark ended due to sequence timeout, then dispose + Volatile.Write(ref _endedFlag, 1); Dispose(); return false; } if (!_sequence.IsNextFrameReady(currentTick)) { return true; } - Console.WriteLine("Frame hit!"); + + // Console.WriteLine("Frame hit!"); // Fast path: if no pending consumers, initialize the counter to current capacity (_size). if (Volatile.Read(ref _pendingSpans) == 0) diff --git a/StgSharp.Common/Timing/TimeSpanProvider.cs b/StgSharp.Common/Timing/TimeSpanProvider.cs index adc5cdc1..67a1d741 100644 --- a/StgSharp.Common/Timing/TimeSpanProvider.cs +++ b/StgSharp.Common/Timing/TimeSpanProvider.cs @@ -41,6 +41,9 @@ namespace StgSharp.Timing private bool _subscribed; private int _emittedSpanCount; // legacy counter + + // internal ended flag (0 = running, 1 = ended) + private int _endedFlag = 0; private readonly TimeSequence _sequence; private readonly TimeSourceProviderBase _timeSource; @@ -62,6 +65,11 @@ namespace StgSharp.Timing _pendingSpans = 0; } + /// + /// True when the provider has reached its end and will not emit further spans. + /// + public bool IsEnded => Volatile.Read(ref _endedFlag) != 0; + public double CurrentSecond { get => (double)_sequence.PreviousFrameTick / _timeSource.Frequency;