new simplified timer. and little other ops
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="TimeProvider"
|
||||
// file="StgSharpTime"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload
|
||||
// Copyright (c) Nitload. All rights reserved.
|
||||
@@ -27,12 +27,8 @@
|
||||
// -----------------------------------------------------------------------
|
||||
using StgSharp.Collections;
|
||||
using StgSharp.Timing;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp
|
||||
@@ -40,9 +36,7 @@ namespace StgSharp
|
||||
public static partial class World
|
||||
{
|
||||
|
||||
internal static StgSharpTime mainTimeProvider = new StgSharpTime();
|
||||
|
||||
internal static TimeSourceProviderBase MainTimeProvider => mainTimeProvider;
|
||||
public static TimeSourceProviderBase MainTimeProvider => StgSharpTime.OnlyInstance;
|
||||
|
||||
}
|
||||
|
||||
@@ -50,40 +44,30 @@ namespace StgSharp
|
||||
{
|
||||
|
||||
private static readonly LinearBag<TimeSpanProvider> _subscribers = new();
|
||||
private static long accuracy;
|
||||
private static readonly Stopwatch mainProvider = new();
|
||||
private static Thread timeProvideThread;
|
||||
|
||||
internal StgSharpTime() { }
|
||||
private StgSharpTime() { }
|
||||
|
||||
public static TimeSourceProviderBase OnlyInstance => World.MainTimeProvider;
|
||||
// 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();
|
||||
|
||||
// speed test
|
||||
long totalMS, internalFrequency = Stopwatch.Frequency;
|
||||
for (int i = 0; i < 100; i++)
|
||||
mainProvider.Start();
|
||||
timeProvideThread = new Thread(ProvideTime)
|
||||
{
|
||||
totalMS = mainProvider.ElapsedTicks / (internalFrequency / (1000L * 1000L));
|
||||
for (int t = 0; t < 10; t++)
|
||||
{
|
||||
if (totalMS < 1000) {
|
||||
_subscribers.Add(
|
||||
new TimeSpanProvider(100L, TimeSpanProvider.DefaultProvider));
|
||||
}
|
||||
}
|
||||
}
|
||||
mainProvider.Stop();
|
||||
_subscribers.Clear();
|
||||
totalMS = mainProvider.ElapsedTicks / (internalFrequency / (1000L * 1000L)) / 100L;
|
||||
accuracy = totalMS * 2 / 3;
|
||||
mainProvider.Reset();
|
||||
timeProvideThread = new Thread(new ThreadStart(ProvideTime));
|
||||
IsBackground = true,
|
||||
Name = "StgSharpTime"
|
||||
};
|
||||
timeProvideThread.Start();
|
||||
}
|
||||
|
||||
@@ -113,29 +97,36 @@ namespace StgSharp
|
||||
|
||||
private void ProvideTime()
|
||||
{
|
||||
long totalMS, internalFrequency = Stopwatch.Frequency;
|
||||
long internalFrequency = Stopwatch.Frequency; // ticks per second
|
||||
lock (mainProvider) {
|
||||
mainProvider.Restart();
|
||||
}
|
||||
|
||||
while (mainProvider.IsRunning)
|
||||
{
|
||||
totalMS = mainProvider.ElapsedTicks / (internalFrequency / (1000L * 1000L));
|
||||
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].CheckTime(totalMS))
|
||||
if (!_subscribers[i].OnSpanTick(elapsedTicks))
|
||||
{
|
||||
_subscribers.RemoveAt(i);
|
||||
DefaultLog.InternalWriteLog(
|
||||
$"Time subscriber {_subscribers[i]} is to be removed.", LogType.Info);
|
||||
i--; // adjust index after removal
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Optional: brief spin to avoid tight loop if frame lengths are large.
|
||||
Thread.SpinWait(20);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,143 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="TimeFunction"
|
||||
// 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
|
||||
namespace StgSharp.Timing
|
||||
{
|
||||
public class TimeFunction
|
||||
{
|
||||
|
||||
private float _beginning, _ending;
|
||||
private TimeSpanProvider _timeProvider;
|
||||
|
||||
internal TimeFunction() { }
|
||||
|
||||
public TimeFunction(TimeSpanProvider timeSpanProvider, Func<float, float> timeRelatedCalculation, float range)
|
||||
{
|
||||
_timeProvider = timeSpanProvider;
|
||||
TimeRelatedCalculation = timeRelatedCalculation;
|
||||
if (timeSpanProvider == null) {
|
||||
return;
|
||||
}
|
||||
_beginning = _timeProvider.CurrentSecond;
|
||||
_ending = _timeProvider.CurrentSecond + range;
|
||||
}
|
||||
|
||||
public virtual bool IsComplete
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timeProvider.CurrentSecond > _ending;
|
||||
}
|
||||
|
||||
public Func<float, float> TimeRelatedCalculation { get; set; }
|
||||
|
||||
public static TimeFunction One
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => new TimeFunctionOne();
|
||||
}
|
||||
|
||||
public TimeSpanProvider TimeSpanProvider
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timeProvider;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
set => _timeProvider = value;
|
||||
}
|
||||
|
||||
public virtual float Calculate()
|
||||
{
|
||||
if (_timeProvider.CurrentSecond < _ending) {
|
||||
return 0;
|
||||
}
|
||||
return TimeRelatedCalculation(_timeProvider.CurrentSecond - _beginning);
|
||||
}
|
||||
|
||||
public static TimeFunction Proportional(TimeSpanProvider time, float begin, float range)
|
||||
{
|
||||
return new TimeFunction(time, (float t) => t + begin, range);
|
||||
}
|
||||
|
||||
public virtual TimeFunction Renew((Func<float, float> func, float range) funcPair)
|
||||
{
|
||||
return new TimeFunction(_timeProvider, funcPair.func, funcPair.range);
|
||||
}
|
||||
|
||||
public virtual TimeFunction Renew(Func<float, float> func, float range)
|
||||
{
|
||||
return new TimeFunction(_timeProvider, func, range);
|
||||
}
|
||||
|
||||
public virtual void Reset()
|
||||
{
|
||||
float range = _ending - _beginning;
|
||||
_beginning = _timeProvider.CurrentSecond;
|
||||
_ending = _beginning + range;
|
||||
}
|
||||
|
||||
public virtual TimeFunction Reverse()
|
||||
{
|
||||
float range = _ending - _beginning;
|
||||
return new TimeFunction(
|
||||
_timeProvider, p => TimeRelatedCalculation(range - p), range);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public sealed class TimeFunctionOne : TimeFunction
|
||||
{
|
||||
|
||||
internal TimeFunctionOne() : base() { }
|
||||
|
||||
public override bool IsComplete => true;
|
||||
|
||||
public override float Calculate()
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
public override TimeFunction Renew((Func<float, float> func, float range) funcPair)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
public override TimeFunction Renew(Func<float, float> func, float range)
|
||||
{
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
|
||||
#pragma warning disable CA1822
|
||||
public override void Reset()
|
||||
{
|
||||
}
|
||||
#pragma warning restore CA1822
|
||||
}
|
||||
}
|
||||
@@ -25,69 +25,193 @@
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using StgSharp.Mathematics;
|
||||
using StgSharp.Timing;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.Common.Timing
|
||||
{
|
||||
public abstract class TimeSequence
|
||||
{
|
||||
|
||||
public ulong BeginningTick { get; protected set; }
|
||||
public long BeginningTick { get; protected set; }
|
||||
|
||||
public ulong EndingTick { get; protected set; }
|
||||
public long EndingTick { get; protected set; }
|
||||
|
||||
public ulong FrameLength { get; protected set; }
|
||||
public long PreviousFrameTick { get; protected set; }
|
||||
|
||||
public static TimeSequence CreateLinear(ulong timeout, ulong length)
|
||||
{
|
||||
return new LinearTimeSequence(timeout, timeout + length, length);
|
||||
}
|
||||
public static TimeSequence CreateLinear(double totalSeconds, double frameSeconds) => new LinearTimeSequence(totalSeconds, frameSeconds);
|
||||
|
||||
public abstract bool IsNextFrameReady(ulong currentTick);
|
||||
public static TimeSequence CreateLogarithmic(
|
||||
double initialSliceSeconds,
|
||||
double totalSeconds,
|
||||
double growthMultiplier,
|
||||
double minSliceSeconds) => new LogTimeSequence(initialSliceSeconds, totalSeconds,
|
||||
growthMultiplier, minSliceSeconds);
|
||||
|
||||
}
|
||||
public abstract bool EndsAt(long currentTick);
|
||||
|
||||
internal sealed class LogTimeSequence : TimeSequence
|
||||
{
|
||||
public abstract bool IsNextFrameReady(long currentTick);
|
||||
|
||||
private float _rate ;
|
||||
|
||||
public LogTimeSequence(ulong beginningTick, ulong endingTick, ulong tickPerDecade)
|
||||
{
|
||||
BeginningTick = beginningTick;
|
||||
EndingTick = endingTick;
|
||||
_rate = Scalar.Pow(10, 1 / tickPerDecade);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool IsNextFrameReady(ulong currentTick)
|
||||
{
|
||||
return (ulong)Math.Log(currentTick - BeginningTick + 1) >= FrameLength;
|
||||
}
|
||||
public abstract void StartFrom(TimeSourceProviderBase source);
|
||||
|
||||
}
|
||||
|
||||
internal sealed class LinearTimeSequence : TimeSequence
|
||||
{
|
||||
|
||||
public LinearTimeSequence(ulong beginningTick, ulong endingTick, ulong tickPerFrame)
|
||||
private readonly double _frameSeconds;
|
||||
private readonly double _totalSeconds;
|
||||
private long _frameLengthTicks;
|
||||
|
||||
public LinearTimeSequence(double totalSeconds, double frameSeconds)
|
||||
{
|
||||
BeginningTick = beginningTick;
|
||||
EndingTick = endingTick;
|
||||
FrameLength = tickPerFrame;
|
||||
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 IsNextFrameReady(ulong currentTick)
|
||||
public override bool EndsAt(long currentTick) => currentTick >= EndingTick;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool IsNextFrameReady(long currentTick)
|
||||
{
|
||||
return currentTick - BeginningTick >= FrameLength;
|
||||
long span = currentTick - PreviousFrameTick;
|
||||
if (span >= _frameLengthTicks && currentTick < EndingTick)
|
||||
{
|
||||
PreviousFrameTick += _frameLengthTicks; // advance start of next frame
|
||||
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. Boundaries (cumulative times since start)
|
||||
/// follow initial * multiplier^k, but each slice duration (difference between adjusted
|
||||
/// boundaries) is at least minSliceSeconds. First slice uses the exact initial length even if
|
||||
/// below minSliceSeconds, subsequent slices enforce minimum. Example: initial=2,
|
||||
/// multiplier=2, total>=8, min=3 => boundaries: 2,5,8 (durations:2,3,3).
|
||||
/// </summary>
|
||||
internal sealed class LogTimeSequence : TimeSequence
|
||||
{
|
||||
|
||||
private readonly double _initialSeconds;
|
||||
private double _lastBoundarySecondsAdjusted; // adjusted cumulative seconds of last emitted boundary (0 at start)
|
||||
private readonly double _minSliceSeconds;
|
||||
private readonly double _multiplier;
|
||||
private double _nextBoundarySecondsAdjusted; // adjusted cumulative seconds for next boundary
|
||||
private readonly double _totalSeconds;
|
||||
|
||||
private int _sliceIndex; // index of next boundary (0 => first boundary at initialSeconds)
|
||||
private long _freq;
|
||||
private long _nextBoundaryTick; // absolute tick for next boundary
|
||||
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) => currentTick >= EndingTick ||
|
||||
_nextBoundarySecondsAdjusted >= _totalSeconds;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public override bool IsNextFrameReady(long currentTick)
|
||||
{
|
||||
if (currentTick < _nextBoundaryTick || currentTick >= EndingTick) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Emit frame: update previous frame tick and prepare next boundary.
|
||||
PreviousFrameTick = _nextBoundaryTick; // boundary just reached
|
||||
_lastBoundarySecondsAdjusted = _nextBoundarySecondsAdjusted;
|
||||
_sliceIndex++;
|
||||
|
||||
// Compute original (unadjusted) next cumulative boundary in seconds.
|
||||
double originalNext = _initialSeconds * Math.Pow(_multiplier, _sliceIndex);
|
||||
|
||||
// Enforce total cap.
|
||||
if (originalNext > _totalSeconds)
|
||||
{
|
||||
originalNext = _totalSeconds;
|
||||
}
|
||||
|
||||
// Compute slice duration based on adjusted previous boundary.
|
||||
double delta = originalNext - _lastBoundarySecondsAdjusted;
|
||||
if (_sliceIndex > 0 && delta < _minSliceSeconds)
|
||||
{
|
||||
// Enforce minimum (except for first slice which uses initial value)
|
||||
originalNext = _lastBoundarySecondsAdjusted + _minSliceSeconds;
|
||||
if (originalNext > _totalSeconds) {
|
||||
originalNext = _totalSeconds;
|
||||
}
|
||||
}
|
||||
_nextBoundarySecondsAdjusted = originalNext;
|
||||
|
||||
if (_nextBoundarySecondsAdjusted >= _totalSeconds)
|
||||
{
|
||||
_nextBoundaryTick = EndingTick; // sequence will end after this boundary
|
||||
} else
|
||||
{
|
||||
_nextBoundaryTick = 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; // first boundary uses exact initial length
|
||||
_nextBoundaryTick = BeginningTick + SecondsToTicks(_nextBoundarySecondsAdjusted);
|
||||
PreviousFrameTick = BeginningTick; // start point
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private long SecondsToTicks(double seconds) => (long)Math.Max(1L, Math.Round(seconds * _freq, MidpointRounding.AwayFromZero));
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,6 +36,8 @@ namespace StgSharp.Timing
|
||||
public abstract class TimeSourceProviderBase
|
||||
{
|
||||
|
||||
public abstract long Frequency { get; }
|
||||
|
||||
protected object SubscribeLock { get; } = new();
|
||||
|
||||
public void AddSubscriber(TimeSpanProvider subscriber)
|
||||
@@ -45,6 +47,8 @@ namespace StgSharp.Timing
|
||||
}
|
||||
}
|
||||
|
||||
public abstract long GetCurrentTimeSpanTick();
|
||||
|
||||
public void RemoveSubscriber(TimeSpanProvider subscriber)
|
||||
{
|
||||
lock (SubscribeLock) {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// file="TimeSpanAwaitingToken"
|
||||
// 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.Collections.Generic;
|
||||
using System.Diagnostics.Contracts;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace StgSharp.Timing
|
||||
{
|
||||
public sealed class TimeSpanAwaitingToken : IDisposable
|
||||
{
|
||||
|
||||
private static readonly ConcurrentBufferStack<int> s_unusedID = new(1);
|
||||
|
||||
private static int s_maxID;
|
||||
|
||||
private EventHandler? _refreshHandler,_missHandler;
|
||||
private int _size, _id;
|
||||
|
||||
private readonly object _participantObject = new();
|
||||
private SemaphoreSlim _awaitingSemaphore;
|
||||
|
||||
private TimeSpanProvider _provider;
|
||||
|
||||
internal TimeSpanAwaitingToken(TimeSpanProvider provider)
|
||||
{
|
||||
if (!s_unusedID.TryPop(out int id)) {
|
||||
id = Interlocked.Increment(ref s_maxID);
|
||||
}
|
||||
TokenID = id;
|
||||
_provider = provider;
|
||||
_size = 1;
|
||||
_awaitingSemaphore = new SemaphoreSlim(0, 1);
|
||||
}
|
||||
|
||||
internal TimeSpanAwaitingToken(TimeSpanProvider provider, int count)
|
||||
{
|
||||
if (!s_unusedID.TryPop(out int id)) {
|
||||
id = Interlocked.Increment(ref s_maxID);
|
||||
}
|
||||
_provider = provider;
|
||||
_size = count;
|
||||
_awaitingSemaphore = new SemaphoreSlim(0, _size);
|
||||
}
|
||||
|
||||
public event EventHandler MissTimeSpanRefreshed
|
||||
{
|
||||
add => _missHandler += value;
|
||||
remove => _missHandler -= value;
|
||||
}
|
||||
|
||||
public event EventHandler TimeSpanRefreshed
|
||||
{
|
||||
add => _refreshHandler += value;
|
||||
remove => _refreshHandler -= value;
|
||||
}
|
||||
|
||||
public int TokenID
|
||||
{
|
||||
get => _id;
|
||||
private set => _id = value;
|
||||
}
|
||||
|
||||
internal SemaphoreSlim AwaitingSemaphoreSlim
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _awaitingSemaphore;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_provider.QuitSpanAwaiting(this);
|
||||
s_unusedID.Push(TokenID);
|
||||
_awaitingSemaphore.Dispose();
|
||||
GC.SuppressFinalize(this);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return _id;
|
||||
}
|
||||
|
||||
public void Participant()
|
||||
{
|
||||
lock (_participantObject)
|
||||
{
|
||||
_size++;
|
||||
SemaphoreSlim newSemaphore = new(_awaitingSemaphore.CurrentCount, _size);
|
||||
Interlocked.Exchange(ref _awaitingSemaphore, newSemaphore).Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
public void WaitNextSpan()
|
||||
{
|
||||
_awaitingSemaphore.Wait();
|
||||
}
|
||||
|
||||
internal void MissRefresh()
|
||||
{
|
||||
_missHandler?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Refresh()
|
||||
{
|
||||
_refreshHandler?.Invoke(this, EventArgs.Empty);
|
||||
}
|
||||
|
||||
~TimeSpanAwaitingToken()
|
||||
{
|
||||
Dispose();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// 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 _pendingSpans; // Number of unconsumed span ticks since last Wait.
|
||||
private int _size, _id;
|
||||
|
||||
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 > 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 ( > 0 means last tick not waited).
|
||||
/// </summary>
|
||||
public int PendingSpanCount => Volatile.Read(ref _pendingSpans);
|
||||
|
||||
internal ManualResetEventSlim AwaitingEvent
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _event;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
s_unusedID.Push(TokenID);
|
||||
_event.Dispose();
|
||||
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)
|
||||
{
|
||||
int 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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
internal void Refresh() => _refreshHandler?.Invoke(this, EventArgs.Empty); // Legacy compatibility.
|
||||
|
||||
~TimeSpanProvider() => Dispose();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -25,207 +25,53 @@
|
||||
//
|
||||
// -----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
using StgSharp.Common.Timing;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp.Timing
|
||||
{
|
||||
/// <summary>
|
||||
/// A microsecond-accuracy timer. It will refresh every a certain time span.
|
||||
/// TimeSpanProvider: uses a bound TimeSequence and TimeSourceProviderBase to publish span
|
||||
/// ticks. Each OnSpanTick advances the sequence; when sequence ends provider unsubscribes.
|
||||
/// </summary>
|
||||
public class TimeSpanProvider
|
||||
public partial class TimeSpanProvider
|
||||
{
|
||||
|
||||
private bool _subscribed;
|
||||
|
||||
private readonly ConcurrentQueue<TimeSpanAwaitingToken> _tokenToAdd = new();
|
||||
private readonly ConcurrentQueue<TimeSpanAwaitingToken> _tokenToQuit;
|
||||
private Counter<int> timeSpanCount = new(-1, 1, 1);
|
||||
private readonly HashSet<TimeSpanAwaitingToken> _awaitingTokens;
|
||||
private long _maxSpanCount;
|
||||
private long _spanBegin;
|
||||
private readonly long _spanLength;
|
||||
private int _emittedSpanCount; // legacy counter
|
||||
private readonly TimeSequence _sequence;
|
||||
private readonly TimeSourceProviderBase _timeSource;
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TimeSpanProvider" /> and define the length of time span.
|
||||
/// Length of a single time span is defined in microseconds.
|
||||
/// </summary>
|
||||
/// <param _label="spanMicroSeconds">
|
||||
///
|
||||
/// </param>
|
||||
/// <param _label="provider">
|
||||
///
|
||||
/// </param>
|
||||
public TimeSpanProvider(long spanMicroSeconds, TimeSourceProviderBase provider)
|
||||
public TimeSpanProvider(TimeSequence sequence, TimeSourceProviderBase provider, int maxWaitCount = 1)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(provider);
|
||||
ArgumentNullException.ThrowIfNull(sequence);
|
||||
_timeSource = provider;
|
||||
_spanLength = spanMicroSeconds;
|
||||
_maxSpanCount = int.MaxValue;
|
||||
_sequence = sequence;
|
||||
_sequence.StartFrom(provider);
|
||||
provider.AddSubscriber(this);
|
||||
_subscribed = true;
|
||||
_awaitingTokens = [];
|
||||
_tokenToQuit = new ConcurrentQueue<TimeSpanAwaitingToken>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TimeSpanProvider" /> and define the length of time span.
|
||||
/// Length of a single time span is defined in seconds.
|
||||
/// </summary>
|
||||
/// <param _label="spanSeconds">
|
||||
///
|
||||
/// </param>
|
||||
/// <param _label="provider">
|
||||
///
|
||||
/// </param>
|
||||
public TimeSpanProvider(double spanSeconds, TimeSourceProviderBase provider)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(provider);
|
||||
_timeSource = provider;
|
||||
_spanLength = (long)(spanSeconds * 1000L * 1000L);
|
||||
_spanBegin = -_spanLength;
|
||||
_maxSpanCount = long.MaxValue;
|
||||
provider.AddSubscriber(this);
|
||||
_subscribed = true;
|
||||
_awaitingTokens = [];
|
||||
_tokenToQuit = new ConcurrentQueue<TimeSpanAwaitingToken>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <see cref="TimeSpanProvider" /> and define the length of time span.
|
||||
/// Length of a single time span is defined in microseconds.
|
||||
/// </summary>
|
||||
/// <param _label="spanLength">
|
||||
///
|
||||
/// </param>
|
||||
/// <param _label="maxSpan">
|
||||
///
|
||||
/// </param>
|
||||
/// <param _label="provider">
|
||||
///
|
||||
/// </param>
|
||||
public TimeSpanProvider(long spanLength, long maxSpan, TimeSourceProviderBase provider)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(provider);
|
||||
if (maxSpan <= 0) {
|
||||
throw new ArgumentException("Sapn count cannot be smaller than zero");
|
||||
if (!s_unusedID.TryPop(out int id)) {
|
||||
id = Interlocked.Increment(ref s_maxID);
|
||||
}
|
||||
_timeSource = provider;
|
||||
_spanLength = spanLength;
|
||||
_spanBegin = -_spanLength;
|
||||
provider.AddSubscriber(this);
|
||||
_subscribed = true;
|
||||
_awaitingTokens = new HashSet<TimeSpanAwaitingToken>();
|
||||
_tokenToQuit = new ConcurrentQueue<TimeSpanAwaitingToken>();
|
||||
TokenID = id;
|
||||
_size = maxWaitCount > 0 ? maxWaitCount : 1;
|
||||
_event = new ManualResetEventSlim(initialState:false, spinCount:0);
|
||||
_pendingSpans = 0;
|
||||
}
|
||||
|
||||
public double SpanLength
|
||||
public double CurrentSecond
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _spanLength * 1.0 / (1000L * 1000L);
|
||||
get => (double)_sequence.PreviousFrameTick / _timeSource.Frequency;
|
||||
}
|
||||
|
||||
public float CurrentSecond
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => timeSpanCount.Value * SpanLengthLowPrecession;
|
||||
}
|
||||
public int CurrentSpan => _emittedSpanCount;
|
||||
|
||||
public float SpanLengthLowPrecession
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _spanLength * 1.0f / (1000 * 1000);
|
||||
}
|
||||
public static TimeSourceProviderBase DefaultProvider => World.MainTimeProvider;
|
||||
|
||||
public int CurrentSpan
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => timeSpanCount.Value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Time source provider provided in <see cref="World" /> internal framework.
|
||||
/// </summary>
|
||||
public static TimeSourceProviderBase DefaultProvider => StgSharpTime.OnlyInstance;
|
||||
|
||||
public TimeSpanAwaitingToken ParticipantSpanAwaiting(int count)
|
||||
{
|
||||
TimeSpanAwaitingToken token = new(this, count);
|
||||
_tokenToAdd.Enqueue(token);
|
||||
return token;
|
||||
}
|
||||
|
||||
public void QuitSpanAwaiting(TimeSpanAwaitingToken token)
|
||||
{
|
||||
_tokenToQuit.Enqueue(token);
|
||||
}
|
||||
|
||||
public void StopSpanProviding()
|
||||
{
|
||||
_ = Interlocked.Exchange(ref _maxSpanCount, 0);
|
||||
}
|
||||
|
||||
public void SubscribeToTimeSource(TimeSourceProviderBase serviceHandler)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(serviceHandler);
|
||||
if (_subscribed) {
|
||||
return;
|
||||
}
|
||||
_subscribed = true;
|
||||
serviceHandler.AddSubscriber(this);
|
||||
}
|
||||
|
||||
public int ToInt32()
|
||||
{
|
||||
return timeSpanCount.Value;
|
||||
}
|
||||
|
||||
internal bool CheckTime(long microseconds)
|
||||
{
|
||||
if (microseconds - _spanBegin < _spanLength) {
|
||||
return true;
|
||||
}
|
||||
foreach (TimeSpanAwaitingToken tokenAdd in _tokenToAdd) {
|
||||
_ = _awaitingTokens.Add(tokenAdd);
|
||||
}
|
||||
_tokenToAdd.Clear();
|
||||
foreach (TimeSpanAwaitingToken tokenQuit in _tokenToQuit)
|
||||
{
|
||||
_ = _awaitingTokens.Remove(tokenQuit);
|
||||
DefaultLog.InternalWriteLog(
|
||||
$"Awaiting token {tokenQuit!.TokenID} has been removed.", LogType.Info);
|
||||
}
|
||||
_tokenToQuit.Clear();
|
||||
|
||||
_spanBegin = microseconds;
|
||||
timeSpanCount++;
|
||||
|
||||
foreach (TimeSpanAwaitingToken item in _awaitingTokens)
|
||||
{
|
||||
if (item.AwaitingSemaphoreSlim.CurrentCount == 0)
|
||||
{
|
||||
_ = item.AwaitingSemaphoreSlim.Release();
|
||||
item.Refresh();
|
||||
} else
|
||||
{
|
||||
item.MissRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
return timeSpanCount < _maxSpanCount;
|
||||
}
|
||||
|
||||
public static explicit operator int([NotNull]TimeSpanProvider provider)
|
||||
{
|
||||
return provider.ToInt32();
|
||||
}
|
||||
public static explicit operator int(TimeSpanProvider provider) => provider?._emittedSpanCount ?? 0;
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user