HLSF use long as aloloc size

This commit is contained in:
Nitload
2026-02-23 12:32:15 +08:00
parent 4bd88ccbca
commit c155cab510
552 changed files with 154266 additions and 154330 deletions
+135 -135
View File
@@ -1,135 +1,135 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="StgSharpTime"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.Collections;
using StgSharp.Timing;
using System;
using System.Diagnostics;
using System.Threading;
namespace StgSharp
{
public static partial class World
{
public static TimeSourceProviderBase MainTimeProvider => StgSharpTime.OnlyInstance;
}
public sealed class StgSharpTime : TimeSourceProviderBase
{
private static readonly LinearBag<TimeSpanProvider> _subscribers = new();
private static readonly Stopwatch mainProvider = new();
private static Thread timeProvideThread;
private StgSharpTime() { }
// Use Stopwatch frequency as base tick frequency (ticks per second).
public override long Frequency => Stopwatch.Frequency;
public static StgSharpTime OnlyInstance { get; } = new StgSharpTime();
public override long GetCurrentTimeSpanTick() => mainProvider.ElapsedTicks;
public sealed override void StartProvidingTime()
{
if (mainProvider.IsRunning) {
return;
}
mainProvider.Start();
timeProvideThread = new Thread(ProvideTime)
{
IsBackground = true,
Name = "StgSharpTime"
};
timeProvideThread.Start();
}
public override void StopProvidingTime()
{
lock (mainProvider) {
mainProvider.Stop();
}
}
public void Terminate()
{
lock (mainProvider) {
mainProvider.Stop();
}
}
protected sealed override void AddSubscriberUnsynced(TimeSpanProvider subscriber)
{
_subscribers.Add(subscriber);
}
protected sealed override void RemoveSubscriberUnsynced(TimeSpanProvider subscriber)
{
_subscribers.Remove(subscriber);
}
private void ProvideTime()
{
long internalFrequency = Stopwatch.Frequency; // ticks per second
lock (mainProvider) {
mainProvider.Restart();
}
while (mainProvider.IsRunning)
{
long elapsedTicks;
lock (mainProvider) {
elapsedTicks = mainProvider.ElapsedTicks;
}
if (_subscribers.Count == 0)
{
Thread.SpinWait(50);
continue;
}
lock (SubscribeLock)
{
for (int i = 0; i < _subscribers.Count; i++)
{
if (!_subscribers[i].OnSpanTick(elapsedTicks))
{
// Console.WriteLine("removed");
_subscribers.RemoveAt(i);
i--; // adjust index after removal
}
}
}
// Optional: brief spin to avoid tight loop if frame lengths are large.
Thread.SpinWait(20);
}
}
}
}
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="StgSharpTime"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.Collections;
using StgSharp.Timing;
using System;
using System.Diagnostics;
using System.Threading;
namespace StgSharp
{
public static partial class World
{
public static TimeSourceProviderBase MainTimeProvider => StgSharpTime.OnlyInstance;
}
public sealed class StgSharpTime : TimeSourceProviderBase
{
private static readonly LinearBag<TimeSpanProvider> _subscribers = new();
private static readonly Stopwatch mainProvider = new();
private static Thread timeProvideThread;
private StgSharpTime() { }
// Use Stopwatch frequency as base tick frequency (ticks per second).
public override long Frequency => Stopwatch.Frequency;
public static StgSharpTime OnlyInstance { get; } = new StgSharpTime();
public override long GetCurrentTimeSpanTick() => mainProvider.ElapsedTicks;
public sealed override void StartProvidingTime()
{
if (mainProvider.IsRunning) {
return;
}
mainProvider.Start();
timeProvideThread = new Thread(ProvideTime)
{
IsBackground = true,
Name = "StgSharpTime"
};
timeProvideThread.Start();
}
public override void StopProvidingTime()
{
lock (mainProvider) {
mainProvider.Stop();
}
}
public void Terminate()
{
lock (mainProvider) {
mainProvider.Stop();
}
}
protected sealed override void AddSubscriberUnsynced(TimeSpanProvider subscriber)
{
_subscribers.Add(subscriber);
}
protected sealed override void RemoveSubscriberUnsynced(TimeSpanProvider subscriber)
{
_subscribers.Remove(subscriber);
}
private void ProvideTime()
{
long internalFrequency = Stopwatch.Frequency; // ticks per second
lock (mainProvider) {
mainProvider.Restart();
}
while (mainProvider.IsRunning)
{
long elapsedTicks;
lock (mainProvider) {
elapsedTicks = mainProvider.ElapsedTicks;
}
if (_subscribers.Count == 0)
{
Thread.SpinWait(50);
continue;
}
lock (SubscribeLock)
{
for (int i = 0; i < _subscribers.Count; i++)
{
if (!_subscribers[i].OnSpanTick(elapsedTicks))
{
// Console.WriteLine("removed");
_subscribers.RemoveAt(i);
i--; // adjust index after removal
}
}
}
// Optional: brief spin to avoid tight loop if frame lengths are large.
Thread.SpinWait(20);
}
}
}
}
+309 -309
View File
@@ -1,309 +1,309 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSequence"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.Timing;
using System;
using System.Runtime.CompilerServices;
namespace StgSharp.Common.Timing
{
public abstract class TimeSequence
{
public long BeginningTick { get; protected set; }
public long EndingTick { get; protected set; }
public long PreviousFrameTick { get; protected set; }
public static TimeSequence CreateLinear(
double totalSeconds,
double frameSeconds
)
{
if (double.IsPositiveInfinity(totalSeconds)) {
return new InfiniteLinearTimeSequence(frameSeconds);
}
return new LinearTimeSequence(totalSeconds, frameSeconds);
}
public static TimeSequence CreateLinearInfinite(
double frameSeconds
)
{
return new InfiniteLinearTimeSequence(frameSeconds);
}
public static TimeSequence CreateLogarithmic(
double initialSliceSeconds,
double totalSeconds,
double growthMultiplier,
double minSliceSeconds
) => new LogTimeSequence(initialSliceSeconds, totalSeconds, growthMultiplier,
minSliceSeconds);
public abstract bool EndsAt(
long currentTick
);
public abstract bool IsNextFrameReady(
long currentTick
);
public abstract void StartFrom(
TimeSourceProviderBase source
);
}
internal sealed class LinearTimeSequence : TimeSequence
{
private readonly double _frameSeconds;
private readonly double _totalSeconds;
private long _frameLengthTicks;
public LinearTimeSequence(
double totalSeconds,
double frameSeconds
)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(totalSeconds);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(frameSeconds);
if (frameSeconds > totalSeconds) {
throw new ArgumentException("Frame length greater than total length.");
}
_totalSeconds = totalSeconds;
_frameSeconds = frameSeconds;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool EndsAt(
long currentTick
) => currentTick >= EndingTick;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool IsNextFrameReady(
long currentTick
)
{
long span = currentTick - PreviousFrameTick;
if (span >= _frameLengthTicks && currentTick < EndingTick)
{
PreviousFrameTick += _frameLengthTicks;
return true;
}
return false;
}
public override void StartFrom(
TimeSourceProviderBase source
)
{
long f = source?.Frequency ?? throw new ArgumentNullException(nameof(source));
_frameLengthTicks = (long)Math.Max(1, _frameSeconds * f);
long totalTicks = (long)Math.Max(_frameLengthTicks, Math.Round(_totalSeconds * f, MidpointRounding.AwayFromZero));
BeginningTick = source.GetCurrentTimeSpanTick();
EndingTick = BeginningTick + totalTicks;
EndingTick = EndingTick < 0 ? long.MaxValue : EndingTick;
PreviousFrameTick = BeginningTick;
}
}
/// <summary>
/// Logarithmic time sequence with growth factor and minimum slice length enforcement.
/// </summary>
internal sealed class LogTimeSequence : TimeSequence
{
private bool _finalEmitted;
private readonly double _initialSeconds;
private double _lastBoundarySecondsAdjusted;
private readonly double _minSliceSeconds;
private readonly double _multiplier;
private double _nextBoundarySecondsAdjusted;
private readonly double _totalSeconds;
private int _sliceIndex;
private long _freq;
private long _nextBoundaryTick;
private long _totalTicks;
public LogTimeSequence(
double initialSliceSeconds,
double totalSeconds,
double growthMultiplier,
double minSliceSeconds
)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialSliceSeconds);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(totalSeconds);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(growthMultiplier);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minSliceSeconds);
if (growthMultiplier < 1.0) {
throw new ArgumentException("growthMultiplier must be >= 1");
}
if (initialSliceSeconds > totalSeconds) {
throw new ArgumentException("Initial slice length greater than total length.");
}
_initialSeconds = initialSliceSeconds;
_totalSeconds = totalSeconds;
_multiplier = growthMultiplier;
_minSliceSeconds = minSliceSeconds;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool EndsAt(
long currentTick
)
{
return currentTick >= EndingTick;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool IsNextFrameReady(
long currentTick
)
{
// Emit final once when reaching or passing end.
if (currentTick >= EndingTick)
{
if (_finalEmitted) {
return false;
}
PreviousFrameTick = EndingTick;
_finalEmitted = true;
return true;
}
// Not reached end: only when we cross the next boundary tick do we emit.
if (currentTick < _nextBoundaryTick)
{
return false;
}
// Advance to this boundary.
PreviousFrameTick = _nextBoundaryTick;
_lastBoundarySecondsAdjusted = _nextBoundarySecondsAdjusted;
_sliceIndex++;
// Compute next cumulative boundary seconds with multiplier, then enforce min-slice and total cap.
double next = _initialSeconds * Math.Pow(_multiplier, _sliceIndex);
if (next > _totalSeconds) {
next = _totalSeconds;
}
double delta = next - _lastBoundarySecondsAdjusted;
if (delta < _minSliceSeconds)
{
next = _lastBoundarySecondsAdjusted + _minSliceSeconds;
if (next > _totalSeconds) {
next = _totalSeconds;
}
}
_nextBoundarySecondsAdjusted = next;
_nextBoundaryTick = (_nextBoundarySecondsAdjusted >= _totalSeconds) ?
EndingTick :
BeginningTick + SecondsToTicks(_nextBoundarySecondsAdjusted);
return true;
}
public override void StartFrom(
TimeSourceProviderBase source
)
{
_freq = source?.Frequency ?? throw new ArgumentNullException(nameof(source));
BeginningTick = source.GetCurrentTimeSpanTick();
_totalTicks = SecondsToTicks(_totalSeconds);
EndingTick = BeginningTick + _totalTicks;
_sliceIndex = 0;
_lastBoundarySecondsAdjusted = 0.0;
_nextBoundarySecondsAdjusted = _initialSeconds;
_nextBoundaryTick = BeginningTick + SecondsToTicks(_nextBoundarySecondsAdjusted);
PreviousFrameTick = BeginningTick;
_finalEmitted = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private long SecondsToTicks(
double seconds
) => (long)Math.Max(1L, Math.Round(seconds * _freq, MidpointRounding.AwayFromZero));
}
/// <summary>
/// Infinite linear time sequence: emits frames at fixed interval and never ends.
/// </summary>
internal sealed class InfiniteLinearTimeSequence : TimeSequence
{
private readonly double _frameSeconds;
private long _frameLengthTicks;
public InfiniteLinearTimeSequence(
double frameSeconds
)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(frameSeconds);
_frameSeconds = frameSeconds;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool EndsAt(
long currentTick
) => false; // never ends
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool IsNextFrameReady(
long currentTick
)
{
long span = currentTick - PreviousFrameTick;
if (span >= _frameLengthTicks)
{
PreviousFrameTick += _frameLengthTicks;
return true;
}
return false;
}
public override void StartFrom(
TimeSourceProviderBase source
)
{
long f = source?.Frequency ?? throw new ArgumentNullException(nameof(source));
_frameLengthTicks = (long)Math.Max(1, _frameSeconds * f);
BeginningTick = source.GetCurrentTimeSpanTick();
EndingTick = long.MaxValue; // sentinel
PreviousFrameTick = BeginningTick;
}
}
}
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSequence"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.Timing;
using System;
using System.Runtime.CompilerServices;
namespace StgSharp.Common.Timing
{
public abstract class TimeSequence
{
public long BeginningTick { get; protected set; }
public long EndingTick { get; protected set; }
public long PreviousFrameTick { get; protected set; }
public static TimeSequence CreateLinear(
double totalSeconds,
double frameSeconds
)
{
if (double.IsPositiveInfinity(totalSeconds)) {
return new InfiniteLinearTimeSequence(frameSeconds);
}
return new LinearTimeSequence(totalSeconds, frameSeconds);
}
public static TimeSequence CreateLinearInfinite(
double frameSeconds
)
{
return new InfiniteLinearTimeSequence(frameSeconds);
}
public static TimeSequence CreateLogarithmic(
double initialSliceSeconds,
double totalSeconds,
double growthMultiplier,
double minSliceSeconds
) => new LogTimeSequence(initialSliceSeconds, totalSeconds, growthMultiplier,
minSliceSeconds);
public abstract bool EndsAt(
long currentTick
);
public abstract bool IsNextFrameReady(
long currentTick
);
public abstract void StartFrom(
TimeSourceProviderBase source
);
}
internal sealed class LinearTimeSequence : TimeSequence
{
private readonly double _frameSeconds;
private readonly double _totalSeconds;
private long _frameLengthTicks;
public LinearTimeSequence(
double totalSeconds,
double frameSeconds
)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(totalSeconds);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(frameSeconds);
if (frameSeconds > totalSeconds) {
throw new ArgumentException("Frame length greater than total length.");
}
_totalSeconds = totalSeconds;
_frameSeconds = frameSeconds;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool EndsAt(
long currentTick
) => currentTick >= EndingTick;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool IsNextFrameReady(
long currentTick
)
{
long span = currentTick - PreviousFrameTick;
if (span >= _frameLengthTicks && currentTick < EndingTick)
{
PreviousFrameTick += _frameLengthTicks;
return true;
}
return false;
}
public override void StartFrom(
TimeSourceProviderBase source
)
{
long f = source?.Frequency ?? throw new ArgumentNullException(nameof(source));
_frameLengthTicks = (long)Math.Max(1, _frameSeconds * f);
long totalTicks = (long)Math.Max(_frameLengthTicks, Math.Round(_totalSeconds * f, MidpointRounding.AwayFromZero));
BeginningTick = source.GetCurrentTimeSpanTick();
EndingTick = BeginningTick + totalTicks;
EndingTick = EndingTick < 0 ? long.MaxValue : EndingTick;
PreviousFrameTick = BeginningTick;
}
}
/// <summary>
/// Logarithmic time sequence with growth factor and minimum slice length enforcement.
/// </summary>
internal sealed class LogTimeSequence : TimeSequence
{
private bool _finalEmitted;
private readonly double _initialSeconds;
private double _lastBoundarySecondsAdjusted;
private readonly double _minSliceSeconds;
private readonly double _multiplier;
private double _nextBoundarySecondsAdjusted;
private readonly double _totalSeconds;
private int _sliceIndex;
private long _freq;
private long _nextBoundaryTick;
private long _totalTicks;
public LogTimeSequence(
double initialSliceSeconds,
double totalSeconds,
double growthMultiplier,
double minSliceSeconds
)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(initialSliceSeconds);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(totalSeconds);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(growthMultiplier);
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(minSliceSeconds);
if (growthMultiplier < 1.0) {
throw new ArgumentException("growthMultiplier must be >= 1");
}
if (initialSliceSeconds > totalSeconds) {
throw new ArgumentException("Initial slice length greater than total length.");
}
_initialSeconds = initialSliceSeconds;
_totalSeconds = totalSeconds;
_multiplier = growthMultiplier;
_minSliceSeconds = minSliceSeconds;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool EndsAt(
long currentTick
)
{
return currentTick >= EndingTick;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool IsNextFrameReady(
long currentTick
)
{
// Emit final once when reaching or passing end.
if (currentTick >= EndingTick)
{
if (_finalEmitted) {
return false;
}
PreviousFrameTick = EndingTick;
_finalEmitted = true;
return true;
}
// Not reached end: only when we cross the next boundary tick do we emit.
if (currentTick < _nextBoundaryTick)
{
return false;
}
// Advance to this boundary.
PreviousFrameTick = _nextBoundaryTick;
_lastBoundarySecondsAdjusted = _nextBoundarySecondsAdjusted;
_sliceIndex++;
// Compute next cumulative boundary seconds with multiplier, then enforce min-slice and total cap.
double next = _initialSeconds * Math.Pow(_multiplier, _sliceIndex);
if (next > _totalSeconds) {
next = _totalSeconds;
}
double delta = next - _lastBoundarySecondsAdjusted;
if (delta < _minSliceSeconds)
{
next = _lastBoundarySecondsAdjusted + _minSliceSeconds;
if (next > _totalSeconds) {
next = _totalSeconds;
}
}
_nextBoundarySecondsAdjusted = next;
_nextBoundaryTick = (_nextBoundarySecondsAdjusted >= _totalSeconds) ?
EndingTick :
BeginningTick + SecondsToTicks(_nextBoundarySecondsAdjusted);
return true;
}
public override void StartFrom(
TimeSourceProviderBase source
)
{
_freq = source?.Frequency ?? throw new ArgumentNullException(nameof(source));
BeginningTick = source.GetCurrentTimeSpanTick();
_totalTicks = SecondsToTicks(_totalSeconds);
EndingTick = BeginningTick + _totalTicks;
_sliceIndex = 0;
_lastBoundarySecondsAdjusted = 0.0;
_nextBoundarySecondsAdjusted = _initialSeconds;
_nextBoundaryTick = BeginningTick + SecondsToTicks(_nextBoundarySecondsAdjusted);
PreviousFrameTick = BeginningTick;
_finalEmitted = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private long SecondsToTicks(
double seconds
) => (long)Math.Max(1L, Math.Round(seconds * _freq, MidpointRounding.AwayFromZero));
}
/// <summary>
/// Infinite linear time sequence: emits frames at fixed interval and never ends.
/// </summary>
internal sealed class InfiniteLinearTimeSequence : TimeSequence
{
private readonly double _frameSeconds;
private long _frameLengthTicks;
public InfiniteLinearTimeSequence(
double frameSeconds
)
{
ArgumentOutOfRangeException.ThrowIfNegativeOrZero(frameSeconds);
_frameSeconds = frameSeconds;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool EndsAt(
long currentTick
) => false; // never ends
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public override bool IsNextFrameReady(
long currentTick
)
{
long span = currentTick - PreviousFrameTick;
if (span >= _frameLengthTicks)
{
PreviousFrameTick += _frameLengthTicks;
return true;
}
return false;
}
public override void StartFrom(
TimeSourceProviderBase source
)
{
long f = source?.Frequency ?? throw new ArgumentNullException(nameof(source));
_frameLengthTicks = (long)Math.Max(1, _frameSeconds * f);
BeginningTick = source.GetCurrentTimeSpanTick();
EndingTick = long.MaxValue; // sentinel
PreviousFrameTick = BeginningTick;
}
}
}
+68 -68
View File
@@ -1,68 +1,68 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSourceProvider"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp;
using System;
using System.Linq;
using System.Threading;
namespace StgSharp.Timing
{
public abstract class TimeSourceProviderBase
{
public abstract long Frequency { get; }
protected object SubscribeLock { get; } = new();
public void AddSubscriber(TimeSpanProvider subscriber)
{
lock (SubscribeLock) {
AddSubscriberUnsynced(subscriber);
}
}
public abstract long GetCurrentTimeSpanTick();
public void RemoveSubscriber(TimeSpanProvider subscriber)
{
lock (SubscribeLock) {
RemoveSubscriberUnsynced(subscriber);
}
}
public abstract void StartProvidingTime();
public abstract void StopProvidingTime();
protected abstract void AddSubscriberUnsynced(TimeSpanProvider subscriber);
protected abstract void RemoveSubscriberUnsynced(TimeSpanProvider subscriber);
}
}
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSourceProvider"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp;
using System;
using System.Linq;
using System.Threading;
namespace StgSharp.Timing
{
public abstract class TimeSourceProviderBase
{
public abstract long Frequency { get; }
protected object SubscribeLock { get; } = new();
public void AddSubscriber(TimeSpanProvider subscriber)
{
lock (SubscribeLock) {
AddSubscriberUnsynced(subscriber);
}
}
public abstract long GetCurrentTimeSpanTick();
public void RemoveSubscriber(TimeSpanProvider subscriber)
{
lock (SubscribeLock) {
RemoveSubscriberUnsynced(subscriber);
}
}
public abstract void StartProvidingTime();
public abstract void StopProvidingTime();
protected abstract void AddSubscriberUnsynced(TimeSpanProvider subscriber);
protected abstract void RemoveSubscriberUnsynced(TimeSpanProvider subscriber);
}
}
@@ -1,238 +1,238 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSpanProvider.Awaiting"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.Timing
{
public partial class TimeSpanProvider
{
private static readonly ConcurrentBufferStack<int> s_unusedID = new ConcurrentBufferStack<int>(8);
private static int s_maxID;
private EventHandler? _refreshHandler, _missHandler;
private int _size, _id;
private long _pendingSpans; // Number of unconsumed span ticks since last Wait.
private readonly ManualResetEventSlim _event = new();
private readonly object _participantObject = new();
public event EventHandler MissTimeSpanRefreshed
{
add => _missHandler += value;
remove => _missHandler -= value;
}
public event EventHandler TimeSpanRefreshed
{
add => _refreshHandler += value;
remove => _refreshHandler -= value;
}
/// <summary>
/// True if there is at least one unconsumed span tick (PendingSpanCount &gt; 0).
/// </summary>
public bool HasPendingSpan => PendingSpanCount > 0;
public int TokenID
{
get => _id;
private set => _id = value;
}
/// <summary>
/// Number of span ticks not yet consumed by Wait ( &gt; 0 means last tick not waited).
/// </summary>
public long PendingSpanCount => Volatile.Read(ref _pendingSpans);
internal ManualResetEventSlim AwaitingEvent
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _event;
}
public void Dispose()
{
// mark provider as ended so external users can observe terminal state
Volatile.Write(ref _endedFlag, 1);
s_unusedID.Push(TokenID);
_event.Set();
GC.SuppressFinalize(this);
}
public override int GetHashCode() => _id;
public void Participant()
{
lock (_participantObject)
{
// Increase the maximum concurrent consumers for the next tick.
_size++;
}
}
public void WaitNextSpan()
{
_event.Wait();
// Consume one span slot (clamp at 0 so multiple subscribers won't make it negative).
while (true)
{
long cur = Volatile.Read(ref _pendingSpans);
if (cur == 0) {
return;
}
if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur)
{
if (cur - 1 == 0)
{
// Last consumer resets the gate for the next tick.
_event.Reset();
}
return;
}
}
}
public async Task WaitNextSpanAsync()
{
await Task.Run(() => _event.Wait()).ConfigureAwait(false);
while (true)
{
long 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;
}
}
}
/// <summary>
/// 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.
/// </summary>
public Task WaitNextSpanAsync(
CancellationToken ct
)
{
// Block the current thread until the event is signaled or cancellation requested.
_event.Wait(ct);
// consume one pending span (same logic as non-cancellable variant)
while (true)
{
long 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.
/// <summary>
///
/// </summary>
/// <param name="currentTick">
/// Tick passed since binded time source.
/// </param>
/// <returns>
/// True if the span provider is working. False if time is over (the sequence ended).
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool OnSpanTick(
long currentTick
)
{
if (_sequence.EndsAt(currentTick))
{
// mark ended due to sequence timeout, then dispose
Volatile.Write(ref _endedFlag, 1);
// Console.WriteLine("Time span ended.");
Dispose();
return false;
}
if (!_sequence.IsNextFrameReady(currentTick)) {
return true;
}
// Console.WriteLine("Frame hit!");
// Fast path: if no pending consumers, initialize the counter to current capacity (_size).
if (Volatile.Read(ref _pendingSpans) == 0)
{
int toIssue = Math.Max(1, Volatile.Read(ref _size));
if (Interlocked.CompareExchange(ref _pendingSpans, toIssue, 0) == 0)
{
// Open the gate for all current waiters (up to `_size`).
_event.Set();
_refreshHandler?.Invoke(this, EventArgs.Empty);
}
} else
{
// A tick is already pending (or was just set by another thread): mark as miss.
_missHandler?.Invoke(this, EventArgs.Empty);
}
return true;
}
~TimeSpanProvider() => Dispose();
}
}
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSpanProvider.Awaiting"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.HighPerformance.Memory;
using System;
using System.Collections.Concurrent;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
namespace StgSharp.Timing
{
public partial class TimeSpanProvider
{
private static readonly ConcurrentBufferStack<int> s_unusedID = new ConcurrentBufferStack<int>(8);
private static int s_maxID;
private EventHandler? _refreshHandler, _missHandler;
private int _size, _id;
private long _pendingSpans; // Number of unconsumed span ticks since last Wait.
private readonly ManualResetEventSlim _event = new();
private readonly object _participantObject = new();
public event EventHandler MissTimeSpanRefreshed
{
add => _missHandler += value;
remove => _missHandler -= value;
}
public event EventHandler TimeSpanRefreshed
{
add => _refreshHandler += value;
remove => _refreshHandler -= value;
}
/// <summary>
/// True if there is at least one unconsumed span tick (PendingSpanCount &gt; 0).
/// </summary>
public bool HasPendingSpan => PendingSpanCount > 0;
public int TokenID
{
get => _id;
private set => _id = value;
}
/// <summary>
/// Number of span ticks not yet consumed by Wait ( &gt; 0 means last tick not waited).
/// </summary>
public long PendingSpanCount => Volatile.Read(ref _pendingSpans);
internal ManualResetEventSlim AwaitingEvent
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _event;
}
public void Dispose()
{
// mark provider as ended so external users can observe terminal state
Volatile.Write(ref _endedFlag, 1);
s_unusedID.Push(TokenID);
_event.Set();
GC.SuppressFinalize(this);
}
public override int GetHashCode() => _id;
public void Participant()
{
lock (_participantObject)
{
// Increase the maximum concurrent consumers for the next tick.
_size++;
}
}
public void WaitNextSpan()
{
_event.Wait();
// Consume one span slot (clamp at 0 so multiple subscribers won't make it negative).
while (true)
{
long cur = Volatile.Read(ref _pendingSpans);
if (cur == 0) {
return;
}
if (Interlocked.CompareExchange(ref _pendingSpans, cur - 1, cur) == cur)
{
if (cur - 1 == 0)
{
// Last consumer resets the gate for the next tick.
_event.Reset();
}
return;
}
}
}
public async Task WaitNextSpanAsync()
{
await Task.Run(() => _event.Wait()).ConfigureAwait(false);
while (true)
{
long 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;
}
}
}
/// <summary>
/// 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.
/// </summary>
public Task WaitNextSpanAsync(
CancellationToken ct
)
{
// Block the current thread until the event is signaled or cancellation requested.
_event.Wait(ct);
// consume one pending span (same logic as non-cancellable variant)
while (true)
{
long 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.
/// <summary>
///
/// </summary>
/// <param name="currentTick">
/// Tick passed since binded time source.
/// </param>
/// <returns>
/// True if the span provider is working. False if time is over (the sequence ended).
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal bool OnSpanTick(
long currentTick
)
{
if (_sequence.EndsAt(currentTick))
{
// mark ended due to sequence timeout, then dispose
Volatile.Write(ref _endedFlag, 1);
// Console.WriteLine("Time span ended.");
Dispose();
return false;
}
if (!_sequence.IsNextFrameReady(currentTick)) {
return true;
}
// Console.WriteLine("Frame hit!");
// Fast path: if no pending consumers, initialize the counter to current capacity (_size).
if (Volatile.Read(ref _pendingSpans) == 0)
{
int toIssue = Math.Max(1, Volatile.Read(ref _size));
if (Interlocked.CompareExchange(ref _pendingSpans, toIssue, 0) == 0)
{
// Open the gate for all current waiters (up to `_size`).
_event.Set();
_refreshHandler?.Invoke(this, EventArgs.Empty);
}
} else
{
// A tick is already pending (or was just set by another thread): mark as miss.
_missHandler?.Invoke(this, EventArgs.Empty);
}
return true;
}
~TimeSpanProvider() => Dispose();
}
}
+93 -93
View File
@@ -1,94 +1,94 @@
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSpanProvider"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.Common.Timing;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace StgSharp.Timing
{
/// <summary>
/// TimeSpanProvider: uses a bound TimeSequence and TimeSourceProviderBase to publish span
/// ticks. Each OnSpanTick advances the sequence; when sequence ends provider unsubscribes.
/// </summary>
public partial class TimeSpanProvider
{
// private bool _subscribed;
private int _emittedSpanCount; // legacy counter
// internal ended flag (0 = running, 1 = ended)
private int _endedFlag;
private readonly TimeSequence _sequence;
private readonly TimeSourceProviderBase _timeSource;
public TimeSpanProvider(
TimeSequence sequence,
TimeSourceProviderBase provider,
int maxWaitCount = 1)
{
ArgumentNullException.ThrowIfNull(provider);
ArgumentNullException.ThrowIfNull(sequence);
_timeSource = provider;
_sequence = sequence;
_sequence.StartFrom(provider);
provider.AddSubscriber(this);
// _subscribed = true;
if (!s_unusedID.TryPop(out int id))
{
id = Interlocked.Increment(ref s_maxID);
}
TokenID = id;
_size = maxWaitCount > 0 ? maxWaitCount : 1;
_event = new ManualResetEventSlim(initialState:false, spinCount:0);
_pendingSpans = 0;
}
/// <summary>
/// True when the provider has reached its end and will not emit further spans.
/// </summary>
public bool IsEnded => Volatile.Read(ref _endedFlag) != 0;
/// <summary>
/// Current emitted time in seconds relative to the sequence beginning.
/// </summary>
public double CurrentSecond
{
get => (double)(_sequence.PreviousFrameTick - _sequence.BeginningTick) / _timeSource.Frequency;
}
public int CurrentSpan => _emittedSpanCount;
public static TimeSourceProviderBase DefaultProvider => World.MainTimeProvider;
public static explicit operator int(TimeSpanProvider provider) => provider?._emittedSpanCount ??
0;
}
//-----------------------------------------------------------------------
// -----------------------------------------------------------------------
// file="TimeSpanProvider"
// Project: StgSharp
// AuthorGroup: Nitload
// Copyright (c) Nitload. All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
// -----------------------------------------------------------------------
// -----------------------------------------------------------------------
using StgSharp.Common.Timing;
using System;
using System.Runtime.CompilerServices;
using System.Threading;
namespace StgSharp.Timing
{
/// <summary>
/// TimeSpanProvider: uses a bound TimeSequence and TimeSourceProviderBase to publish span
/// ticks. Each OnSpanTick advances the sequence; when sequence ends provider unsubscribes.
/// </summary>
public partial class TimeSpanProvider
{
// private bool _subscribed;
private int _emittedSpanCount; // legacy counter
// internal ended flag (0 = running, 1 = ended)
private int _endedFlag;
private readonly TimeSequence _sequence;
private readonly TimeSourceProviderBase _timeSource;
public TimeSpanProvider(
TimeSequence sequence,
TimeSourceProviderBase provider,
int maxWaitCount = 1)
{
ArgumentNullException.ThrowIfNull(provider);
ArgumentNullException.ThrowIfNull(sequence);
_timeSource = provider;
_sequence = sequence;
_sequence.StartFrom(provider);
provider.AddSubscriber(this);
// _subscribed = true;
if (!s_unusedID.TryPop(out int id))
{
id = Interlocked.Increment(ref s_maxID);
}
TokenID = id;
_size = maxWaitCount > 0 ? maxWaitCount : 1;
_event = new ManualResetEventSlim(initialState:false, spinCount:0);
_pendingSpans = 0;
}
/// <summary>
/// True when the provider has reached its end and will not emit further spans.
/// </summary>
public bool IsEnded => Volatile.Read(ref _endedFlag) != 0;
/// <summary>
/// Current emitted time in seconds relative to the sequence beginning.
/// </summary>
public double CurrentSecond
{
get => (double)(_sequence.PreviousFrameTick - _sequence.BeginningTick) / _timeSource.Frequency;
}
public int CurrentSpan => _emittedSpanCount;
public static TimeSourceProviderBase DefaultProvider => World.MainTimeProvider;
public static explicit operator int(TimeSpanProvider provider) => provider?._emittedSpanCount ??
0;
}
}