MulAvailable
This commit is contained in:
@@ -0,0 +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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
// file="TimeFunction.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Text;
|
||||
|
||||
namespace StgSharp.Timing
|
||||
{
|
||||
public class TimeFunction
|
||||
{
|
||||
|
||||
private float _beginning, _ending;
|
||||
private Func<float, float> _timeRelatedCalculation;
|
||||
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
|
||||
{
|
||||
get => _timeProvider.CurrentSecond > _ending;
|
||||
}
|
||||
|
||||
public Func<float, float> TimeRelatedCalculation
|
||||
{
|
||||
get { return _timeRelatedCalculation; }
|
||||
set { _timeRelatedCalculation = value; }
|
||||
}
|
||||
|
||||
public static TimeFunction One
|
||||
{
|
||||
get => new TimeFunctionOne();
|
||||
}
|
||||
|
||||
public TimeSpanProvider TimeSpanProvider
|
||||
{
|
||||
get { return _timeProvider; }
|
||||
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 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
|
||||
{
|
||||
get => 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 new void Reset()
|
||||
#pragma warning restore CA1822
|
||||
{ }
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
// file="TimeProvider.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp
|
||||
{
|
||||
public static partial class World
|
||||
{
|
||||
|
||||
internal static StgSharpTime mainTimeProvider = new StgSharpTime();
|
||||
|
||||
internal static TimeSourceProviderBase MainTimeProvider => mainTimeProvider;
|
||||
|
||||
}
|
||||
|
||||
public sealed class StgSharpTime : TimeSourceProviderBase
|
||||
{
|
||||
|
||||
private static List<TimeSpanProvider> subscriberList = new List<TimeSpanProvider>();
|
||||
private static long accuracy;
|
||||
private static Stopwatch mainProvider = new Stopwatch();
|
||||
private static Thread timeProvideThread;
|
||||
|
||||
internal StgSharpTime() { }
|
||||
|
||||
public static TimeSourceProviderBase OnlyInstance => World.MainTimeProvider;
|
||||
|
||||
public override sealed void StartProvidingTime()
|
||||
{
|
||||
if( mainProvider.IsRunning ) {
|
||||
return;
|
||||
}
|
||||
mainProvider.Start();
|
||||
|
||||
//speed test
|
||||
long totalMS, internalFrequncy = Stopwatch.Frequency;
|
||||
for( int i = 0; i < 100; i++ )
|
||||
{
|
||||
totalMS = mainProvider.ElapsedTicks / ( internalFrequncy / ( 1000L * 1000L ) );
|
||||
for( int t = 0; t < 10; t++ )
|
||||
{
|
||||
if( totalMS < 1000 ) {
|
||||
subscriberList.Add(
|
||||
new TimeSpanProvider( 100L, TimeSpanProvider.DefaultProvider ) );
|
||||
}
|
||||
}
|
||||
}
|
||||
mainProvider.Stop();
|
||||
subscriberList.Clear();
|
||||
totalMS = mainProvider.ElapsedTicks / ( internalFrequncy / ( 1000L * 1000L ) ) / 100L;
|
||||
accuracy = ( totalMS * 2 ) / 3;
|
||||
mainProvider.Reset();
|
||||
timeProvideThread = new Thread( new ThreadStart( ProvideTime ) );
|
||||
timeProvideThread.Start();
|
||||
}
|
||||
|
||||
public override void StopProvidingTime()
|
||||
{
|
||||
lock( mainProvider ) {
|
||||
mainProvider.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
public void Terminate()
|
||||
{
|
||||
lock( mainProvider ) {
|
||||
mainProvider.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
protected override sealed void AddSubscriberUnsynced( TimeSpanProvider subscriber )
|
||||
{
|
||||
subscriberList.Add( subscriber );
|
||||
}
|
||||
|
||||
protected override sealed void RemoveSubscriberUnsynced( TimeSpanProvider subscriber )
|
||||
{
|
||||
subscriberList.Remove( subscriber );
|
||||
}
|
||||
|
||||
private void ProvideTime()
|
||||
{
|
||||
long totalMS, internalFrequncy = Stopwatch.Frequency;
|
||||
lock( mainProvider ) {
|
||||
mainProvider.Restart();
|
||||
}
|
||||
while( mainProvider.IsRunning )
|
||||
{
|
||||
totalMS = mainProvider.ElapsedTicks / ( internalFrequncy / ( 1000L * 1000L ) );
|
||||
if( subscriberList.Count == 0 )
|
||||
{
|
||||
continue;
|
||||
}
|
||||
CheckSubscriberSemaphore.Wait();
|
||||
List<TimeSpanProvider> toRemoveList = new List<TimeSpanProvider>();
|
||||
foreach( TimeSpanProvider subscriber in subscriberList )
|
||||
{
|
||||
if( !subscriber.CheckTime( totalMS ) )
|
||||
{
|
||||
toRemoveList.Add( subscriber );
|
||||
InternalIO.InternalWriteLog(
|
||||
$"Time subscriber {subscriber} is to be removed.", LogType.Info );
|
||||
}
|
||||
;
|
||||
}
|
||||
if( toRemoveList.Count != 0 )
|
||||
{
|
||||
foreach( TimeSpanProvider subscriber in toRemoveList ) {
|
||||
subscriberList.Remove( subscriber );
|
||||
}
|
||||
}
|
||||
CheckSubscriberSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,218 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// 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) => new LinearTimeSequence(totalSeconds, 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) => 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));
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
// file="TimeSourceProvider.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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
|
||||
{
|
||||
|
||||
private SemaphoreSlim _addSubscriberSemaphore = new SemaphoreSlim( 1, 1 );
|
||||
|
||||
protected SemaphoreSlim CheckSubscriberSemaphore
|
||||
{
|
||||
get => _addSubscriberSemaphore;
|
||||
}
|
||||
|
||||
public void AddSubscriber( TimeSpanProvider subscriber )
|
||||
{
|
||||
try
|
||||
{
|
||||
_addSubscriberSemaphore.Wait();
|
||||
AddSubscriberUnsynced( subscriber );
|
||||
}
|
||||
catch( Exception )
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_addSubscriberSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveSubscriber( TimeSpanProvider subscriber )
|
||||
{
|
||||
_addSubscriberSemaphore.Wait();
|
||||
try
|
||||
{
|
||||
RemoveSubscriberUnsynced( subscriber );
|
||||
}
|
||||
catch( Exception )
|
||||
{
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_addSubscriberSemaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public abstract void StartProvidingTime();
|
||||
|
||||
public abstract void StopProvidingTime();
|
||||
|
||||
protected abstract void AddSubscriberUnsynced( TimeSpanProvider subscriber );
|
||||
|
||||
protected abstract void RemoveSubscriberUnsynced( TimeSpanProvider subscriber );
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,170 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
// file="TimeSpanAwaitingToken.cs"
|
||||
// Project: StgSharp
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.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
|
||||
{
|
||||
#nullable enable
|
||||
|
||||
public sealed class TimeSpanAwaitingToken : IDisposable
|
||||
{
|
||||
|
||||
private static readonly ConcurrentStack<int> s_unusedID = new ConcurrentStack<int>( [0] );
|
||||
private static int s_maxID;
|
||||
private static readonly object s_lock = new object();
|
||||
|
||||
private EventHandler? _refreshHandler,_missHandler;
|
||||
private int _size, _id;
|
||||
|
||||
private object _participantObject = new object();
|
||||
private SemaphoreSlim _awaitingSemaphore;
|
||||
|
||||
private TimeSpanProvider _provider;
|
||||
|
||||
internal TimeSpanAwaitingToken( TimeSpanProvider provider )
|
||||
{
|
||||
if( s_unusedID.Count == 1 )
|
||||
{
|
||||
lock( s_lock )
|
||||
{
|
||||
s_unusedID.TryPop( out int id );
|
||||
TokenID = id;
|
||||
s_unusedID.Push( s_maxID++ );
|
||||
}
|
||||
} else
|
||||
{
|
||||
s_unusedID.TryPop( out int id );
|
||||
TokenID = id;
|
||||
}
|
||||
_provider = provider;
|
||||
_size = 1;
|
||||
_awaitingSemaphore = new SemaphoreSlim( 0, 1 );
|
||||
}
|
||||
|
||||
internal TimeSpanAwaitingToken( TimeSpanProvider provider, int count )
|
||||
{
|
||||
if( s_unusedID.Count == 1 )
|
||||
{
|
||||
lock( s_lock )
|
||||
{
|
||||
s_unusedID.TryPop( out int id );
|
||||
TokenID = id;
|
||||
s_unusedID.Push( s_maxID++ );
|
||||
}
|
||||
} else
|
||||
{
|
||||
s_unusedID.TryPop( out int id );
|
||||
TokenID = id;
|
||||
}
|
||||
_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
|
||||
{
|
||||
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 SemaphoreSlim(
|
||||
_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();
|
||||
}
|
||||
|
||||
}
|
||||
#nullable restore
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
//-----------------------------------------------------------------------
|
||||
// -----------------------------------------------------------------------
|
||||
// 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()
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
int cur = Volatile.Read(ref _pendingSpans);
|
||||
if (cur == 0)
|
||||
{
|
||||
_event.Dispose();
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
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.
|
||||
|
||||
/// <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();
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
//-----------------------------------------------------------------------
|
||||
//-----------------------------------------------------------------------
|
||||
// file="TimeSpanProvider.cs"
|
||||
// Project: StepVisualizer
|
||||
// AuthorGroup: Nitload Space
|
||||
// Copyright (c) Nitload Space. 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.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading;
|
||||
|
||||
namespace StgSharp.Timing
|
||||
{
|
||||
/// <summary>
|
||||
/// A microsecond-accuracy timer. It will refresh every a certain time span.
|
||||
/// </summary>
|
||||
public class TimeSpanProvider
|
||||
{
|
||||
|
||||
private bool _subscribed;
|
||||
|
||||
private ConcurrentQueue<TimeSpanAwaitingToken> _tokenToAdd = new ConcurrentQueue<TimeSpanAwaitingToken>(
|
||||
);
|
||||
private ConcurrentQueue<TimeSpanAwaitingToken> _tokenToQuit;
|
||||
private Counter<int> timeSpanCount = new Counter<int>( -1, 1, 1 );
|
||||
private HashSet<TimeSpanAwaitingToken> _awaitingTokens;
|
||||
private long _maxSpanCount;
|
||||
private long _spanBegin;
|
||||
private readonly long _spanLength;
|
||||
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 )
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull( provider );
|
||||
_timeSource = provider;
|
||||
_spanLength = spanMicroSeconds;
|
||||
_maxSpanCount = int.MaxValue;
|
||||
provider.AddSubscriber( this );
|
||||
_subscribed = true;
|
||||
_awaitingTokens = new HashSet<TimeSpanAwaitingToken>();
|
||||
_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 = new HashSet<TimeSpanAwaitingToken>();
|
||||
_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" );
|
||||
}
|
||||
_timeSource = provider;
|
||||
_spanLength = spanLength;
|
||||
_spanBegin = -_spanLength;
|
||||
provider.AddSubscriber( this );
|
||||
_subscribed = true;
|
||||
_awaitingTokens = new HashSet<TimeSpanAwaitingToken>();
|
||||
_tokenToQuit = new ConcurrentQueue<TimeSpanAwaitingToken>();
|
||||
}
|
||||
|
||||
public double SpanLength
|
||||
{
|
||||
get => ( _spanLength * 1.0 ) / ( 1000L * 1000L );
|
||||
}
|
||||
|
||||
public float CurrentSecond
|
||||
{
|
||||
get => timeSpanCount.Value * SpanLengthLowPrecession;
|
||||
}
|
||||
|
||||
public float SpanLengthLowPrecession
|
||||
{
|
||||
get => ( _spanLength * 1.0f ) / ( 1000 * 1000 );
|
||||
}
|
||||
|
||||
public int CurrentSpan
|
||||
{
|
||||
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 TimeSpanAwaitingToken( 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 );
|
||||
InternalIO.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();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user