fix: Fixes pooled timer detach (#1115)
- [X] Fixes timers not returning on detach
This commit is contained in:
parent
9c374861d9
commit
a4168dc219
7 changed files with 804 additions and 816 deletions
|
|
@ -20,211 +20,207 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Server
|
||||
namespace Server;
|
||||
|
||||
public partial class Timer
|
||||
{
|
||||
public partial class Timer
|
||||
private static string FormatDelegate(Delegate callback) =>
|
||||
callback == null ? "null" : $"{callback.Method.DeclaringType?.FullName ?? ""}.{callback.Method.Name}";
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(Action callback) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan delay, Action callback) => DelayCall(delay, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, Action callback) =>
|
||||
DelayCall(delay, interval, 0, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan interval, int count, Action callback) =>
|
||||
DelayCall(TimeSpan.Zero, interval, count, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
{
|
||||
private static string FormatDelegate(Delegate callback) =>
|
||||
callback == null ? "null" : $"{callback.Method.DeclaringType?.FullName ?? ""}.{callback.Method.Name}";
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(Action callback) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan delay, Action callback) => DelayCall(delay, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, Action callback) =>
|
||||
DelayCall(delay, interval, 0, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan interval, int count, Action callback) =>
|
||||
DelayCall(TimeSpan.Zero, interval, count, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer DelayCall(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
{
|
||||
DelayCallTimer t = new DelayCallTimer(delay, interval, count, callback);
|
||||
t.Start();
|
||||
DelayCallTimer t = new DelayCallTimer(delay, interval, count, callback);
|
||||
t.Start();
|
||||
#if DEBUG_TIMERS
|
||||
t._allowFinalization = true;
|
||||
t._allowFinalization = true;
|
||||
#endif
|
||||
|
||||
return t;
|
||||
return t;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(Action callback) => StartTimer(TimeSpan.Zero, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, Action callback) => StartTimer(delay, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback) =>
|
||||
StartTimer(delay, interval, 0, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan interval, int count, Action callback) =>
|
||||
StartTimer(TimeSpan.Zero, interval, count, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
{
|
||||
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
|
||||
t._returnOnDetach = true;
|
||||
t.Start();
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
DelayCallTimer._stackTraces[t.GetHashCode()] = $"{callback.Method.Name}\n{new StackTrace()}";
|
||||
#endif
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(TimeSpan.Zero, TimeSpan.Zero, 1, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(delay, TimeSpan.Zero, 1, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(delay, interval, 0, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan interval, int count, Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(TimeSpan.Zero, interval, count, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback, out TimerExecutionToken token)
|
||||
{
|
||||
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
|
||||
t.Start();
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
DelayCallTimer._stackTraces[t.GetHashCode()] = $"{callback.Method.Name}\n{new StackTrace()}";
|
||||
#endif
|
||||
token = new TimerExecutionToken(t);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer Pause(TimeSpan ms) => new(ms);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer Pause(int ms) => Pause(TimeSpan.FromMilliseconds(ms));
|
||||
|
||||
public sealed class DelayCallTimer : Timer, INotifyCompletion
|
||||
{
|
||||
internal bool _returnOnDetach;
|
||||
#if DEBUG_TIMERS
|
||||
internal bool _allowFinalization;
|
||||
#endif
|
||||
private Action _continuation;
|
||||
private bool _complete;
|
||||
|
||||
internal DelayCallTimer(TimeSpan delay, TimeSpan interval, int count, Action callback) : base(
|
||||
delay,
|
||||
interval,
|
||||
count
|
||||
) => _continuation = callback;
|
||||
|
||||
internal DelayCallTimer(TimeSpan delay) : base(delay)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
_allowFinalization = true;
|
||||
#endif
|
||||
Start();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(Action callback) => StartTimer(TimeSpan.Zero, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, Action callback) => StartTimer(delay, TimeSpan.Zero, 1, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback) =>
|
||||
StartTimer(delay, interval, 0, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan interval, int count, Action callback) =>
|
||||
StartTimer(TimeSpan.Zero, interval, count, callback);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
protected override void OnTick()
|
||||
{
|
||||
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
|
||||
t._returnOnDetach = true;
|
||||
t.Start();
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
DelayCallTimer._stackTraces[t.GetHashCode()] = new StackTrace().ToString();
|
||||
#endif
|
||||
_complete = true;
|
||||
_continuation?.Invoke();
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(TimeSpan.Zero, TimeSpan.Zero, 1, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(delay, TimeSpan.Zero, 1, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(delay, interval, 0, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan interval, int count, Action callback, out TimerExecutionToken token) =>
|
||||
StartTimer(TimeSpan.Zero, interval, count, callback, out token);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static void StartTimer(TimeSpan delay, TimeSpan interval, int count, Action callback, out TimerExecutionToken token)
|
||||
internal override void OnDetach()
|
||||
{
|
||||
DelayCallTimer t = DelayCallTimer.GetTimer(delay, interval, count, callback);
|
||||
t.Start();
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
DelayCallTimer._stackTraces[t.GetHashCode()] = new StackTrace().ToString();
|
||||
#endif
|
||||
token = new TimerExecutionToken(t);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer Pause(TimeSpan ms) => new(ms);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static DelayCallTimer Pause(int ms) => Pause(TimeSpan.FromMilliseconds(ms));
|
||||
|
||||
public sealed class DelayCallTimer : Timer, INotifyCompletion
|
||||
{
|
||||
internal bool _returnOnDetach;
|
||||
#if DEBUG_TIMERS
|
||||
internal bool _allowFinalization;
|
||||
#endif
|
||||
private Action _continuation;
|
||||
private bool _complete;
|
||||
|
||||
internal DelayCallTimer(TimeSpan delay, TimeSpan interval, int count, Action callback) : base(
|
||||
delay,
|
||||
interval,
|
||||
count
|
||||
) =>
|
||||
_continuation = callback;
|
||||
|
||||
internal DelayCallTimer(TimeSpan delay) : base(delay)
|
||||
if (Running)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
_allowFinalization = true;
|
||||
#endif
|
||||
Start();
|
||||
logger.Error("Timer is returned while still running!\n{StackTrace}", new StackTrace());
|
||||
return;
|
||||
}
|
||||
|
||||
protected override void OnTick()
|
||||
if (_returnOnDetach)
|
||||
{
|
||||
_complete = true;
|
||||
_continuation?.Invoke();
|
||||
}
|
||||
|
||||
internal override void OnDetach()
|
||||
{
|
||||
if (Running)
|
||||
#if DEBUG_TIMERS
|
||||
_stackTraces.Remove(GetHashCode());
|
||||
#endif
|
||||
|
||||
if (_poolCount >= _poolCapacity)
|
||||
{
|
||||
logger.Error("Timer is returned while still running!\n{StackTrace}", new StackTrace());
|
||||
#if DEBUG_TIMERS
|
||||
logger.Warning("DelayCallTimer pool reached maximum of {Capacity} timers", _poolCapacity);
|
||||
_allowFinalization = true;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (_returnOnDetach)
|
||||
{
|
||||
// Increment the version so if this is called from OnTick() and another timer is started, we don't have a problem
|
||||
// Version++;
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
_stackTraces.Remove(GetHashCode());
|
||||
#endif
|
||||
|
||||
if (_poolCount >= _poolCapacity)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
logger.Warning("DelayCallTimer pool reached maximum of {Capacity} timers", _poolCapacity);
|
||||
_allowFinalization = true;
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
_continuation = null;
|
||||
_returnOnDetach = false;
|
||||
ReturnToPool(1, this, this);
|
||||
}
|
||||
_continuation = null;
|
||||
_returnOnDetach = false;
|
||||
ReturnToPool(1, this, this);
|
||||
}
|
||||
}
|
||||
|
||||
public static DelayCallTimer GetTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
public static DelayCallTimer GetTimer(TimeSpan delay, TimeSpan interval, int count, Action callback)
|
||||
{
|
||||
var timer = GetFromPool();
|
||||
if (timer != null)
|
||||
{
|
||||
var timer = GetFromPool();
|
||||
if (timer != null)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
logger.Information("Getting from pool: ({Count} / {Capacity})", _poolCount, _poolCapacity);
|
||||
logger.Information("Getting from pool: ({Count} / {Capacity})", _poolCount, _poolCapacity);
|
||||
#endif
|
||||
|
||||
timer.Init(delay, interval, count);
|
||||
timer._continuation = callback;
|
||||
timer._returnOnDetach = false;
|
||||
timer.Init(delay, interval, count);
|
||||
timer._continuation = callback;
|
||||
timer._returnOnDetach = false;
|
||||
#if DEBUG_TIMERS
|
||||
timer._allowFinalization = false;
|
||||
timer._allowFinalization = false;
|
||||
#endif
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
_timerPoolDepletionAmount++;
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
logger.Warning("Timer pool depleted and timer was allocated.\n{StackTrace}", new StackTrace());
|
||||
#endif
|
||||
return new DelayCallTimer(delay, interval, count, callback);
|
||||
return timer;
|
||||
}
|
||||
|
||||
public override string ToString() => $"DelayCallTimer[{FormatDelegate(_continuation)}]";
|
||||
_timerPoolDepletionAmount++;
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
internal static Dictionary<int, string> _stackTraces = new();
|
||||
logger.Warning("Timer pool depleted and timer was allocated.\n{StackTrace}", new StackTrace());
|
||||
#endif
|
||||
return new DelayCallTimer(delay, interval, count, callback);
|
||||
}
|
||||
|
||||
~DelayCallTimer()
|
||||
public override string ToString() => $"DelayCallTimer[{FormatDelegate(_continuation)}]";
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
internal static Dictionary<int, string> _stackTraces = new();
|
||||
|
||||
~DelayCallTimer()
|
||||
{
|
||||
if (!_allowFinalization)
|
||||
{
|
||||
if (!_allowFinalization)
|
||||
{
|
||||
logger.Warning("Pooled timer was not returned to the pool.\n{StackTrace}", _stackTraces[GetHashCode()]);
|
||||
}
|
||||
logger.Warning("Pooled timer was not returned to the pool.\n{StackTrace}", _stackTraces[GetHashCode()]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public DelayCallTimer GetAwaiter() => this;
|
||||
public DelayCallTimer GetAwaiter() => this;
|
||||
|
||||
public bool IsCompleted => _complete;
|
||||
public bool IsCompleted => _complete;
|
||||
|
||||
public void OnCompleted(Action continuation) => _continuation = continuation;
|
||||
public void OnCompleted(Action continuation) => _continuation = continuation;
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
}
|
||||
public void GetResult()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,227 +16,226 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Server
|
||||
namespace Server;
|
||||
|
||||
public partial class Timer
|
||||
{
|
||||
public partial class Timer
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(Action<T> callback, T state) =>
|
||||
DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, state);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(TimeSpan delay, Action<T> callback, T state) =>
|
||||
DelayCall(delay, TimeSpan.Zero, 1, callback, state);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(TimeSpan delay, TimeSpan interval, Action<T> callback, T state) =>
|
||||
DelayCall(delay, interval, 0, callback, state);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(TimeSpan delay, TimeSpan interval, int count, Action<T> callback, T state) =>
|
||||
new DelayStateCallTimer<T>(delay, interval, count, callback, state).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(Action<T1, T2> callback, T1 t1, T2 t2) =>
|
||||
DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(TimeSpan delay, Action<T1, T2> callback, T1 t1, T2 t2) =>
|
||||
DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(TimeSpan delay, TimeSpan interval, Action<T1, T2> callback, T1 t1, T2 t2) =>
|
||||
DelayCall(delay, interval, 0, callback, t1, t2);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2> callback,
|
||||
T1 t1, T2 t2
|
||||
) => new DelayStateCallTimer<T1, T2>(delay, interval, count, callback, t1, t2).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(Action<T1, T2, T3> callback, T1 t1, T2 t2, T3 t3) =>
|
||||
DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(
|
||||
TimeSpan delay, Action<T1, T2, T3> callback, T1 t1, T2 t2, T3 t3
|
||||
) => DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(
|
||||
TimeSpan delay, TimeSpan interval, Action<T1, T2, T3> callback,
|
||||
T1 t1, T2 t2, T3 t3
|
||||
) => DelayCall(delay, interval, 0, callback, t1, t2, t3);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(
|
||||
TimeSpan delay, TimeSpan interval, int count,
|
||||
Action<T1, T2, T3> callback, T1 t1, T2 t2, T3 t3
|
||||
) => new DelayStateCallTimer<T1, T2, T3>(delay, interval, count, callback, t1, t2, t3).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
Action<T1, T2, T3, T4> callback, T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3, t4);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
TimeSpan delay, Action<T1, T2, T3, T4> callback,
|
||||
T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3, t4);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
TimeSpan delay, TimeSpan interval,
|
||||
Action<T1, T2, T3, T4> callback, T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => DelayCall(delay, interval, 0, callback, t1, t2, t3, t4);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
TimeSpan delay, TimeSpan interval, int count,
|
||||
Action<T1, T2, T3, T4> callback, T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4>(delay, interval, count, callback, t1, t2, t3, t4).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
TimeSpan delay,
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(delay, TimeSpan.Zero, 1, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
TimeSpan delay, TimeSpan interval,
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(delay, interval, 0, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
TimeSpan delay, TimeSpan interval, int count,
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(delay, interval, count, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
private class DelayStateCallTimer<T> : Timer
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(Action<T> callback, T state) =>
|
||||
DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, state);
|
||||
private readonly T _t1;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(TimeSpan delay, Action<T> callback, T state) =>
|
||||
DelayCall(delay, TimeSpan.Zero, 1, callback, state);
|
||||
public DelayStateCallTimer(TimeSpan delay, TimeSpan interval, int count, Action<T> callback, T state)
|
||||
: base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = state;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(TimeSpan delay, TimeSpan interval, Action<T> callback, T state) =>
|
||||
DelayCall(delay, interval, 0, callback, state);
|
||||
public Action<T> Callback { get; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T>(TimeSpan delay, TimeSpan interval, int count, Action<T> callback, T state) =>
|
||||
new DelayStateCallTimer<T>(delay, interval, count, callback, state).Start();
|
||||
protected override void OnTick() => Callback?.Invoke(_t1);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(Action<T1, T2> callback, T1 t1, T2 t2) =>
|
||||
DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2);
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(TimeSpan delay, Action<T1, T2> callback, T1 t1, T2 t2) =>
|
||||
DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2);
|
||||
private class DelayStateCallTimer<T1, T2> : Timer
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(TimeSpan delay, TimeSpan interval, Action<T1, T2> callback, T1 t1, T2 t2) =>
|
||||
DelayCall(delay, interval, 0, callback, t1, t2);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2>(
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2> callback,
|
||||
T1 t1, T2 t2
|
||||
) => new DelayStateCallTimer<T1, T2>(delay, interval, count, callback, t1, t2).Start();
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(Action<T1, T2, T3> callback, T1 t1, T2 t2, T3 t3) =>
|
||||
DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3);
|
||||
public Action<T1, T2> Callback { get; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(
|
||||
TimeSpan delay, Action<T1, T2, T3> callback, T1 t1, T2 t2, T3 t3
|
||||
) => DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3);
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(
|
||||
TimeSpan delay, TimeSpan interval, Action<T1, T2, T3> callback,
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
|
||||
private class DelayStateCallTimer<T1, T2, T3> : Timer
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
private readonly T3 _t3;
|
||||
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2, T3> callback,
|
||||
T1 t1, T2 t2, T3 t3
|
||||
) => DelayCall(delay, interval, 0, callback, t1, t2, t3);
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
_t3 = t3;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3>(
|
||||
TimeSpan delay, TimeSpan interval, int count,
|
||||
Action<T1, T2, T3> callback, T1 t1, T2 t2, T3 t3
|
||||
) => new DelayStateCallTimer<T1, T2, T3>(delay, interval, count, callback, t1, t2, t3).Start();
|
||||
public Action<T1, T2, T3> Callback { get; }
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
Action<T1, T2, T3, T4> callback, T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => DelayCall(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3, t4);
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2, _t3);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
TimeSpan delay, Action<T1, T2, T3, T4> callback,
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
|
||||
private class DelayStateCallTimer<T1, T2, T3, T4> : Timer
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
private readonly T3 _t3;
|
||||
private readonly T4 _t4;
|
||||
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2, T3, T4> callback,
|
||||
T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => DelayCall(delay, TimeSpan.Zero, 1, callback, t1, t2, t3, t4);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
TimeSpan delay, TimeSpan interval,
|
||||
Action<T1, T2, T3, T4> callback, T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => DelayCall(delay, interval, 0, callback, t1, t2, t3, t4);
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4>(
|
||||
TimeSpan delay, TimeSpan interval, int count,
|
||||
Action<T1, T2, T3, T4> callback, T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4>(delay, interval, count, callback, t1, t2, t3, t4).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(TimeSpan.Zero, TimeSpan.Zero, 1, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
TimeSpan delay,
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(delay, TimeSpan.Zero, 1, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
TimeSpan delay, TimeSpan interval,
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(delay, interval, 0, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public static Timer DelayCall<T1, T2, T3, T4, T5>(
|
||||
TimeSpan delay, TimeSpan interval, int count,
|
||||
Action<T1, T2, T3, T4, T5> callback, T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) => new DelayStateCallTimer<T1, T2, T3, T4, T5>(delay, interval, count, callback, t1, t2, t3, t4, t5).Start();
|
||||
|
||||
private class DelayStateCallTimer<T> : Timer
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
private readonly T _t1;
|
||||
|
||||
public DelayStateCallTimer(TimeSpan delay, TimeSpan interval, int count, Action<T> callback, T state)
|
||||
: base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = state;
|
||||
}
|
||||
|
||||
public Action<T> Callback { get; }
|
||||
|
||||
protected override void OnTick() => Callback?.Invoke(_t1);
|
||||
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
_t3 = t3;
|
||||
_t4 = t4;
|
||||
}
|
||||
|
||||
private class DelayStateCallTimer<T1, T2> : Timer
|
||||
public Action<T1, T2, T3, T4> Callback { get; }
|
||||
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2, _t3, _t4);
|
||||
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
|
||||
private class DelayStateCallTimer<T1, T2, T3, T4, T5> : Timer
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
private readonly T3 _t3;
|
||||
private readonly T4 _t4;
|
||||
private readonly T5 _t5;
|
||||
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2, T3, T4, T5> callback,
|
||||
T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2> callback,
|
||||
T1 t1, T2 t2
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
}
|
||||
|
||||
public Action<T1, T2> Callback { get; }
|
||||
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2);
|
||||
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
_t3 = t3;
|
||||
_t4 = t4;
|
||||
_t5 = t5;
|
||||
}
|
||||
|
||||
private class DelayStateCallTimer<T1, T2, T3> : Timer
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
private readonly T3 _t3;
|
||||
public Action<T1, T2, T3, T4, T5> Callback { get; }
|
||||
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2, T3> callback,
|
||||
T1 t1, T2 t2, T3 t3
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
_t3 = t3;
|
||||
}
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2, _t3, _t4, _t5);
|
||||
|
||||
public Action<T1, T2, T3> Callback { get; }
|
||||
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2, _t3);
|
||||
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
|
||||
private class DelayStateCallTimer<T1, T2, T3, T4> : Timer
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
private readonly T3 _t3;
|
||||
private readonly T4 _t4;
|
||||
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2, T3, T4> callback,
|
||||
T1 t1, T2 t2, T3 t3, T4 t4
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
_t3 = t3;
|
||||
_t4 = t4;
|
||||
}
|
||||
|
||||
public Action<T1, T2, T3, T4> Callback { get; }
|
||||
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2, _t3, _t4);
|
||||
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
|
||||
private class DelayStateCallTimer<T1, T2, T3, T4, T5> : Timer
|
||||
{
|
||||
private readonly T1 _t1;
|
||||
private readonly T2 _t2;
|
||||
private readonly T3 _t3;
|
||||
private readonly T4 _t4;
|
||||
private readonly T5 _t5;
|
||||
|
||||
public DelayStateCallTimer(
|
||||
TimeSpan delay, TimeSpan interval, int count, Action<T1, T2, T3, T4, T5> callback,
|
||||
T1 t1, T2 t2, T3 t3, T4 t4, T5 t5
|
||||
) : base(delay, interval, count)
|
||||
{
|
||||
Callback = callback;
|
||||
_t1 = t1;
|
||||
_t2 = t2;
|
||||
_t3 = t3;
|
||||
_t4 = t4;
|
||||
_t5 = t5;
|
||||
}
|
||||
|
||||
public Action<T1, T2, T3, T4, T5> Callback { get; }
|
||||
|
||||
protected override void OnTick() => Callback?.Invoke(_t1, _t2, _t3, _t4, _t5);
|
||||
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
public override string ToString() => $"DelayStateCall[{FormatDelegate(Callback)}]";
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,122 +15,115 @@
|
|||
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Server
|
||||
namespace Server;
|
||||
|
||||
public partial class Timer
|
||||
{
|
||||
public partial class Timer
|
||||
private const int _timerPoolDepletionThreshold = 128; // Maximum timers allocated in a single tick before we force adjust
|
||||
private static int _timerPoolDepletionAmount; // Amount the pool has been depleted by
|
||||
private static int _maxPoolCapacity;
|
||||
private static int _poolCapacity;
|
||||
private static int _poolCount;
|
||||
private static DelayCallTimer _poolHead;
|
||||
private static int _isRefilling;
|
||||
|
||||
public static void CheckTimerPool()
|
||||
{
|
||||
private const int _timerPoolDepletionThreshold = 128; // Maximum timers allocated in a single tick before we force adjust
|
||||
private static int _timerPoolDepletionAmount; // Amount the pool has been depleted by
|
||||
private static int _maxPoolCapacity;
|
||||
private static int _poolCapacity;
|
||||
private static int _poolCount;
|
||||
private static DelayCallTimer _poolHead;
|
||||
|
||||
public static void CheckTimerPool()
|
||||
// Anything less than this threshold and we are ok with the number of allocations.
|
||||
if (_timerPoolDepletionAmount < _timerPoolDepletionThreshold)
|
||||
{
|
||||
// Anything less than this threshold and we are ok with the number of allocations.
|
||||
if (_timerPoolDepletionAmount < _timerPoolDepletionThreshold)
|
||||
{
|
||||
_timerPoolDepletionAmount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
var growthFactor = Math.DivRem(_timerPoolDepletionAmount, _poolCapacity, out var rem);
|
||||
var amountToGrow = _poolCapacity * (growthFactor + (rem > 0 ? 1 : 0));
|
||||
var amountToRefill = Math.Min(_maxPoolCapacity, amountToGrow);
|
||||
|
||||
var maximumHit = amountToGrow > amountToRefill ? " Maximum pool size has been reached." : "";
|
||||
var warningMessage = $"Timer pool depleted by {{Amount}}. Refilling with {{AmountRefill}}.{maximumHit}";
|
||||
|
||||
logger.Warning(warningMessage, _timerPoolDepletionAmount, amountToRefill);
|
||||
RefillPoolAsync(amountToRefill);
|
||||
_timerPoolDepletionAmount = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
public static void ConfigureTimerPool()
|
||||
{
|
||||
_poolCapacity = ServerConfiguration.GetOrUpdateSetting("timer.initialPoolCapacity", 1024);
|
||||
_maxPoolCapacity = ServerConfiguration.GetOrUpdateSetting("timer.maxPoolCapacity", _poolCapacity * 16);
|
||||
var growthFactor = Math.DivRem(_timerPoolDepletionAmount, _poolCapacity, out var rem);
|
||||
var amountToGrow = _poolCapacity * (growthFactor + (rem > 0 ? 1 : 0));
|
||||
var amountToRefill = Math.Min(_maxPoolCapacity, amountToGrow);
|
||||
|
||||
RefillPool(_poolCapacity, out var head, out var tail);
|
||||
ReturnToPool(_poolCapacity, head, tail);
|
||||
}
|
||||
var maximumHit = amountToGrow > amountToRefill ? " Maximum pool size has been reached." : "";
|
||||
var warningMessage = $"Timer pool depleted by {{Amount}}. Refilling with {{AmountRefill}}.{maximumHit}";
|
||||
|
||||
private static void ReturnToPool(int amount, DelayCallTimer head, DelayCallTimer tail)
|
||||
{
|
||||
tail.Attach(_poolHead);
|
||||
_poolHead = head;
|
||||
_poolCount += amount;
|
||||
logger.Warning(warningMessage, _timerPoolDepletionAmount, amountToRefill);
|
||||
RefillPoolAsync(amountToRefill);
|
||||
_timerPoolDepletionAmount = 0;
|
||||
}
|
||||
|
||||
public static void ConfigureTimerPool()
|
||||
{
|
||||
_poolCapacity = ServerConfiguration.GetOrUpdateSetting("timer.initialPoolCapacity", 1024);
|
||||
_maxPoolCapacity = ServerConfiguration.GetOrUpdateSetting("timer.maxPoolCapacity", _poolCapacity * 16);
|
||||
|
||||
RefillPool(_poolCapacity, out var head, out var tail);
|
||||
ReturnToPool(_poolCapacity, head, tail);
|
||||
}
|
||||
|
||||
private static void ReturnToPool(int amount, DelayCallTimer head, DelayCallTimer tail)
|
||||
{
|
||||
tail.Attach(_poolHead);
|
||||
_poolHead = head;
|
||||
_poolCount += amount;
|
||||
#if DEBUG_TIMERS
|
||||
logger.Information("Returning to pool. ({Count} / {Capacity})", _poolCount, _poolCapacity);
|
||||
logger.Information("Returning to pool. ({Count} / {Capacity})", _poolCount, _poolCapacity);
|
||||
#endif
|
||||
}
|
||||
|
||||
private static DelayCallTimer GetFromPool()
|
||||
{
|
||||
if (_poolHead == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
private static DelayCallTimer GetFromPool()
|
||||
{
|
||||
if (_poolHead == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var timer = _poolHead;
|
||||
_poolHead = _poolHead._nextTimer as DelayCallTimer;
|
||||
timer.Detach();
|
||||
_poolCount--;
|
||||
|
||||
var timer = _poolHead;
|
||||
_poolHead = _poolHead._nextTimer as DelayCallTimer;
|
||||
timer.Detach();
|
||||
_poolCount--;
|
||||
return timer;
|
||||
}
|
||||
|
||||
return timer;
|
||||
}
|
||||
|
||||
internal static void RefillPool(int amount, out DelayCallTimer head, out DelayCallTimer tail)
|
||||
{
|
||||
internal static void RefillPool(int amount, out DelayCallTimer head, out DelayCallTimer tail)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
logger.Information("Filling pool with {Amount} timers.", amount);
|
||||
logger.Information("Filling pool with {Amount} timers.", amount);
|
||||
#endif
|
||||
|
||||
head = null;
|
||||
tail = null;
|
||||
head = null;
|
||||
tail = null;
|
||||
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
var timer = new DelayCallTimer(TimeSpan.Zero, TimeSpan.Zero, 0, null);
|
||||
timer.Attach(head);
|
||||
|
||||
if (i == 0)
|
||||
{
|
||||
tail = timer;
|
||||
}
|
||||
|
||||
head = timer;
|
||||
}
|
||||
}
|
||||
|
||||
internal static void RefillPoolAsync(int amountToRefill)
|
||||
for (var i = 0; i < amount; i++)
|
||||
{
|
||||
ThreadPool.UnsafeQueueUserWorkItem(
|
||||
static amount =>
|
||||
{
|
||||
RefillPool(amount, out var head, out var tail);
|
||||
var timer = new DelayCallTimer(TimeSpan.Zero, TimeSpan.Zero, 0, null);
|
||||
timer.Attach(head);
|
||||
|
||||
// Run this on the core thread
|
||||
Core.LoopContext.Post(
|
||||
state =>
|
||||
{
|
||||
if (state == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (i == 0)
|
||||
{
|
||||
tail = timer;
|
||||
}
|
||||
|
||||
var (listHead, listTail) = ((DelayCallTimer, DelayCallTimer))state;
|
||||
ReturnToPool(amount, listHead, listTail);
|
||||
_poolCapacity = amount;
|
||||
},
|
||||
(head, tail)
|
||||
);
|
||||
},
|
||||
amountToRefill,
|
||||
false
|
||||
);
|
||||
head = timer;
|
||||
}
|
||||
}
|
||||
|
||||
internal static async void RefillPoolAsync(int amountToRefill)
|
||||
{
|
||||
if (Interlocked.CompareExchange(ref _isRefilling, 0, 1) == 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var (headTimer, tailTimer) = await Task.Run(
|
||||
() =>
|
||||
{
|
||||
RefillPool(amountToRefill, out var head, out var tail);
|
||||
return (head, tail);
|
||||
}
|
||||
);
|
||||
|
||||
ReturnToPool(amountToRefill, headTimer, tailTimer);
|
||||
_poolCapacity = amountToRefill;
|
||||
_isRefilling = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,270 +18,269 @@ using System.Collections.Generic;
|
|||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Server
|
||||
namespace Server;
|
||||
|
||||
public partial class Timer
|
||||
{
|
||||
public partial class Timer
|
||||
private const int _ringSizePowerOf2 = 12;
|
||||
private const int _ringSize = 1 << _ringSizePowerOf2; // 4096
|
||||
private const int _ringLayers = 3;
|
||||
private const int _tickRatePowerOf2 = 3;
|
||||
private const int _tickRate = 1 << _tickRatePowerOf2; // 8ms
|
||||
|
||||
private static readonly Timer[][] _rings = new Timer[_ringLayers][];
|
||||
private static readonly int[] _ringIndexes = new int[_ringLayers];
|
||||
|
||||
private static long _lastTickTurned = -1;
|
||||
private static bool _timerWheelExecuting;
|
||||
|
||||
public static void Init(long tickCount)
|
||||
{
|
||||
private const int _ringSizePowerOf2 = 12;
|
||||
private const int _ringSize = 1 << _ringSizePowerOf2; // 4096
|
||||
private const int _ringLayers = 3;
|
||||
private const int _tickRatePowerOf2 = 3;
|
||||
private const int _tickRate = 1 << _tickRatePowerOf2; // 8ms
|
||||
_lastTickTurned = tickCount;
|
||||
|
||||
private static readonly Timer[][] _rings = new Timer[_ringLayers][];
|
||||
private static readonly int[] _ringIndexes = new int[_ringLayers];
|
||||
|
||||
private static long _lastTickTurned = -1;
|
||||
private static bool _timerWheelExecuting;
|
||||
|
||||
public static void Init(long tickCount)
|
||||
for (int i = 0; i < _rings.Length; i++)
|
||||
{
|
||||
_lastTickTurned = tickCount;
|
||||
|
||||
for (int i = 0; i < _rings.Length; i++)
|
||||
{
|
||||
_rings[i] = new Timer[_ringSize];
|
||||
_ringIndexes[i] = 0;
|
||||
}
|
||||
_rings[i] = new Timer[_ringSize];
|
||||
_ringIndexes[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Slice(long tickCount)
|
||||
public static void Slice(long tickCount)
|
||||
{
|
||||
var deltaSinceTurn = tickCount - _lastTickTurned;
|
||||
while (deltaSinceTurn >= _tickRate)
|
||||
{
|
||||
var deltaSinceTurn = tickCount - _lastTickTurned;
|
||||
while (deltaSinceTurn >= _tickRate)
|
||||
{
|
||||
deltaSinceTurn -= _tickRate;
|
||||
_lastTickTurned += _tickRate;
|
||||
Turn();
|
||||
}
|
||||
deltaSinceTurn -= _tickRate;
|
||||
_lastTickTurned += _tickRate;
|
||||
Turn();
|
||||
}
|
||||
}
|
||||
|
||||
private static void Turn()
|
||||
private static void Turn()
|
||||
{
|
||||
_timerWheelExecuting = true;
|
||||
var turnNextWheel = false;
|
||||
|
||||
var _executingRings = new Timer[_ringLayers];
|
||||
|
||||
// Detach the chain from the timer wheel. This allows adding timers to the same slot during execution.
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
_timerWheelExecuting = true;
|
||||
var turnNextWheel = false;
|
||||
|
||||
var _executingRings = new Timer[_ringLayers];
|
||||
|
||||
// Detach the chain from the timer wheel. This allows adding timers to the same slot during execution.
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
if (i == 0 || turnNextWheel)
|
||||
{
|
||||
if (i == 0 || turnNextWheel)
|
||||
var ringIndex = ++_ringIndexes[i];
|
||||
turnNextWheel = ringIndex >= _ringSize;
|
||||
|
||||
if (turnNextWheel)
|
||||
{
|
||||
var ringIndex = ++_ringIndexes[i];
|
||||
turnNextWheel = ringIndex >= _ringSize;
|
||||
ringIndex = _ringIndexes[i] = 0;
|
||||
}
|
||||
|
||||
if (turnNextWheel)
|
||||
_executingRings[i] = _rings[i][ringIndex];
|
||||
_rings[i][ringIndex] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_executingRings[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
var timer = _executingRings[i];
|
||||
if (timer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
var next = timer._nextTimer;
|
||||
|
||||
timer.Detach();
|
||||
|
||||
// Check to see if it's running just in case it was stopped by another timer
|
||||
if (timer.Running)
|
||||
{
|
||||
if (i > 0 && timer._remaining > 0)
|
||||
{
|
||||
ringIndex = _ringIndexes[i] = 0;
|
||||
// Promote
|
||||
AddTimer(timer, timer._remaining);
|
||||
}
|
||||
|
||||
_executingRings[i] = _rings[i][ringIndex];
|
||||
_rings[i][ringIndex] = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
_executingRings[i] = null;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
var timer = _executingRings[i];
|
||||
if (timer == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
do
|
||||
{
|
||||
var next = timer._nextTimer;
|
||||
|
||||
timer.Detach();
|
||||
|
||||
// Check to see if it's running just in case it was stopped by another timer
|
||||
if (timer.Running)
|
||||
else
|
||||
{
|
||||
if (i > 0 && timer._remaining > 0)
|
||||
{
|
||||
// Promote
|
||||
AddTimer(timer, timer._remaining);
|
||||
}
|
||||
else
|
||||
{
|
||||
Execute(timer);
|
||||
}
|
||||
Execute(timer);
|
||||
}
|
||||
}
|
||||
|
||||
if (!timer.Running)
|
||||
{
|
||||
timer.OnDetach();
|
||||
}
|
||||
if (!timer.Running)
|
||||
{
|
||||
timer.OnDetach();
|
||||
}
|
||||
|
||||
timer = next;
|
||||
} while (timer != null);
|
||||
}
|
||||
|
||||
_timerWheelExecuting = false;
|
||||
timer = next;
|
||||
} while (timer != null);
|
||||
}
|
||||
|
||||
private static void Execute(Timer timer)
|
||||
_timerWheelExecuting = false;
|
||||
}
|
||||
|
||||
private static void Execute(Timer timer)
|
||||
{
|
||||
var finished = timer.Count != 0 && ++timer.Index >= timer.Count;
|
||||
|
||||
var version = timer.Version;
|
||||
|
||||
var prof = timer.GetProfile();
|
||||
prof?.Start();
|
||||
timer.OnTick();
|
||||
prof?.Finish();
|
||||
|
||||
// If the timer has not been stopped, and it has not been altered (restarted, returned etc)
|
||||
if (timer.Running && timer.Version == version)
|
||||
{
|
||||
var finished = timer.Count != 0 && ++timer.Index >= timer.Count;
|
||||
|
||||
var version = timer.Version;
|
||||
|
||||
var prof = timer.GetProfile();
|
||||
prof?.Start();
|
||||
timer.OnTick();
|
||||
prof?.Finish();
|
||||
|
||||
// If the timer has not been stopped, and it has not been altered (restarted, returned etc)
|
||||
if (timer.Running && timer.Version == version)
|
||||
if (finished)
|
||||
{
|
||||
if (finished)
|
||||
{
|
||||
timer.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
timer.Delay = timer.Interval;
|
||||
timer.Next = Core.Now + timer.Interval;
|
||||
AddTimer(timer, (long)timer.Delay.TotalMilliseconds);
|
||||
}
|
||||
timer.Stop();
|
||||
}
|
||||
else
|
||||
{
|
||||
timer.Delay = timer.Interval;
|
||||
timer.Next = Core.Now + timer.Interval;
|
||||
AddTimer(timer, (long)timer.Delay.TotalMilliseconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AddTimer(Timer timer, long delay)
|
||||
{
|
||||
private static void AddTimer(Timer timer, long delay)
|
||||
{
|
||||
#if DEBUG_TIMERS
|
||||
var originalDelay = delay;
|
||||
var originalDelay = delay;
|
||||
#endif
|
||||
delay = Math.Max(0, delay);
|
||||
delay = Math.Max(0, delay);
|
||||
|
||||
var resolutionPowerOf2 = _tickRatePowerOf2;
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
var resolutionPowerOf2 = _tickRatePowerOf2;
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
var resolution = 1L << resolutionPowerOf2;
|
||||
var nextResolutionPowerOf2 = resolutionPowerOf2 + _ringSizePowerOf2;
|
||||
var max = 1L << nextResolutionPowerOf2;
|
||||
if (delay < max)
|
||||
{
|
||||
var resolution = 1L << resolutionPowerOf2;
|
||||
var nextResolutionPowerOf2 = resolutionPowerOf2 + _ringSizePowerOf2;
|
||||
var max = 1L << nextResolutionPowerOf2;
|
||||
if (delay < max)
|
||||
var remaining = delay & (resolution - 1);
|
||||
var slot = (delay >> resolutionPowerOf2) + _ringIndexes[i] + (remaining > 0 ? 1 : 0);
|
||||
|
||||
// Round up if we have a delay of 0
|
||||
if (delay == 0)
|
||||
{
|
||||
var remaining = delay & (resolution - 1);
|
||||
var slot = (delay >> resolutionPowerOf2) + _ringIndexes[i] + (remaining > 0 ? 1 : 0);
|
||||
|
||||
// Round up if we have a delay of 0
|
||||
if (delay == 0)
|
||||
{
|
||||
slot++;
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
if (slot >= _ringSize)
|
||||
{
|
||||
slot -= _ringSize;
|
||||
}
|
||||
|
||||
timer.Attach(_rings[i][slot]);
|
||||
timer._remaining = remaining;
|
||||
timer._ring = i;
|
||||
timer._slot = (int)slot;
|
||||
|
||||
_rings[i][slot] = timer;
|
||||
|
||||
return;
|
||||
slot++;
|
||||
remaining = 0;
|
||||
}
|
||||
|
||||
// The remaining amount until we turn this ring
|
||||
delay -= resolution * (_ringSize - _ringIndexes[i]);
|
||||
resolutionPowerOf2 = nextResolutionPowerOf2;
|
||||
}
|
||||
|
||||
// TODO: Handle timers > 17yrs
|
||||
#if DEBUG_TIMERS
|
||||
logger.Error("Timer is more than max duration. ({Duration})", originalDelay);
|
||||
#endif
|
||||
}
|
||||
|
||||
public static void DumpInfo(TextWriter tw)
|
||||
{
|
||||
tw.WriteLine("Date: {0}\n", Core.Now.ToLocalTime());
|
||||
tw.WriteLine("Pool - Count: {0}; Size {1}\n", _poolCount - _timerPoolDepletionAmount, _poolCapacity);
|
||||
|
||||
var total = 0.0;
|
||||
var hash = new Dictionary<string, int>();
|
||||
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
for (var j = 0; j < _ringSize; j++)
|
||||
if (slot >= _ringSize)
|
||||
{
|
||||
var t = _rings[i][j];
|
||||
if (t == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
while (t != null)
|
||||
{
|
||||
var name = t.ToString();
|
||||
|
||||
hash.TryGetValue(name, out var count);
|
||||
hash[name] = count + 1;
|
||||
|
||||
total++;
|
||||
|
||||
t = t?._nextTimer;
|
||||
}
|
||||
slot -= _ringSize;
|
||||
}
|
||||
|
||||
timer.Attach(_rings[i][slot]);
|
||||
timer._remaining = remaining;
|
||||
timer._ring = i;
|
||||
timer._slot = (int)slot;
|
||||
|
||||
_rings[i][slot] = timer;
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
tw.WriteLine("Timers:");
|
||||
|
||||
foreach (var (name, count) in hash.OrderByDescending(o => o.Value))
|
||||
{
|
||||
var percent = count / total;
|
||||
var line = $"{count:#,0} ({percent:P1})";
|
||||
// 6 - 15 / 8 = 1
|
||||
var tabs = new string('\t', line.Length < 12 ? 2 : 1);
|
||||
tw.WriteLine($"{line}{tabs}{name}");
|
||||
}
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
tw.WriteLine($"{Environment.NewLine}Stack Traces:");
|
||||
foreach (var kvp in DelayCallTimer._stackTraces)
|
||||
{
|
||||
tw.WriteLine(kvp.Value);
|
||||
tw.WriteLine();
|
||||
}
|
||||
#endif
|
||||
|
||||
tw.WriteLine();
|
||||
tw.WriteLine();
|
||||
// The remaining amount until we turn this ring
|
||||
delay -= resolution * (_ringSize - _ringIndexes[i]);
|
||||
resolutionPowerOf2 = nextResolutionPowerOf2;
|
||||
}
|
||||
|
||||
public static void ClearAllTimers(long tickCount)
|
||||
{
|
||||
_lastTickTurned = tickCount;
|
||||
// TODO: Handle timers > 17yrs
|
||||
#if DEBUG_TIMERS
|
||||
logger.Error("Timer is more than max duration. ({Duration})", originalDelay);
|
||||
#endif
|
||||
}
|
||||
|
||||
foreach (var t in _rings)
|
||||
public static void DumpInfo(TextWriter tw)
|
||||
{
|
||||
tw.WriteLine($"Date: {Core.Now.ToLocalTime()}\n");
|
||||
tw.WriteLine($"Pool - Count: {_poolCount - _timerPoolDepletionAmount}; Size {_poolCapacity}\n");
|
||||
|
||||
var total = 0.0;
|
||||
var hash = new Dictionary<string, int>();
|
||||
|
||||
for (var i = 0; i < _ringLayers; i++)
|
||||
{
|
||||
for (var j = 0; j < _ringSize; j++)
|
||||
{
|
||||
var t = _rings[i][j];
|
||||
if (t == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _ringSize; i++)
|
||||
while (t != null)
|
||||
{
|
||||
var node = t[i];
|
||||
Timer next;
|
||||
var name = t.ToString();
|
||||
|
||||
do
|
||||
{
|
||||
next = node?._nextTimer;
|
||||
node?.Stop();
|
||||
} while (next != null);
|
||||
hash.TryGetValue(name, out var count);
|
||||
hash[name] = count + 1;
|
||||
|
||||
total++;
|
||||
|
||||
t = t?._nextTimer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
tw.WriteLine("Timers:");
|
||||
|
||||
foreach (var (name, count) in hash.OrderByDescending(o => o.Value))
|
||||
{
|
||||
var percent = count / total;
|
||||
var line = $"{count:#,0} ({percent:P1})";
|
||||
// 6 - 15 / 8 = 1
|
||||
var tabs = new string('\t', line.Length < 12 ? 2 : 1);
|
||||
tw.WriteLine($"{line}{tabs}{name}");
|
||||
}
|
||||
|
||||
#if DEBUG_TIMERS
|
||||
tw.WriteLine($"{Environment.NewLine}Stack Traces:");
|
||||
foreach (var kvp in DelayCallTimer._stackTraces)
|
||||
{
|
||||
tw.WriteLine(kvp.Value);
|
||||
tw.WriteLine();
|
||||
}
|
||||
#endif
|
||||
|
||||
tw.WriteLine();
|
||||
tw.WriteLine();
|
||||
}
|
||||
|
||||
public static void ClearAllTimers(long tickCount)
|
||||
{
|
||||
_lastTickTurned = tickCount;
|
||||
|
||||
foreach (var t in _rings)
|
||||
{
|
||||
if (t == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < _ringSize; i++)
|
||||
{
|
||||
var node = t[i];
|
||||
Timer next;
|
||||
|
||||
do
|
||||
{
|
||||
next = node?._nextTimer;
|
||||
node?.Stop();
|
||||
} while (next != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,144 +17,145 @@ using System;
|
|||
using Server.Diagnostics;
|
||||
using Server.Logging;
|
||||
|
||||
namespace Server
|
||||
namespace Server;
|
||||
|
||||
public partial class Timer
|
||||
{
|
||||
public partial class Timer
|
||||
protected internal static readonly ILogger logger = LogFactory.GetLogger(typeof(Timer));
|
||||
|
||||
public static void Configure()
|
||||
{
|
||||
protected internal static readonly ILogger logger = LogFactory.GetLogger(typeof(Timer));
|
||||
ConfigureTimerPool();
|
||||
}
|
||||
|
||||
public static void Configure()
|
||||
// We need to know what ring/slot we are in so we can be removed if we are "head" of the link list.
|
||||
private int _ring;
|
||||
private int _slot;
|
||||
private long _remaining;
|
||||
private Timer _nextTimer;
|
||||
private Timer _prevTimer;
|
||||
|
||||
public Timer(TimeSpan delay) => Init(delay, TimeSpan.Zero, 1);
|
||||
|
||||
public Timer(TimeSpan interval, int count) => Init(interval, interval, count);
|
||||
|
||||
public Timer(TimeSpan delay, TimeSpan interval, int count = 0) => Init(delay, interval, count);
|
||||
|
||||
protected void Init(TimeSpan delay, TimeSpan interval, int count)
|
||||
{
|
||||
Running = false;
|
||||
Delay = delay;
|
||||
Index = 0;
|
||||
Interval = interval;
|
||||
Count = count;
|
||||
_nextTimer = null;
|
||||
_prevTimer = null;
|
||||
Next = Core.Now + Delay;
|
||||
|
||||
var prof = GetProfile();
|
||||
|
||||
if (prof != null)
|
||||
{
|
||||
ConfigureTimerPool();
|
||||
prof.Created++;
|
||||
}
|
||||
}
|
||||
|
||||
// We need to know what ring/slot we are in so we can be removed if we are "head" of the link list.
|
||||
private int _ring;
|
||||
private int _slot;
|
||||
private long _remaining;
|
||||
private Timer _nextTimer;
|
||||
private Timer _prevTimer;
|
||||
protected int Version { get; set; } // Used to determine if a timer was altered and we should abandon it.
|
||||
|
||||
public Timer(TimeSpan delay) => Init(delay, TimeSpan.Zero, 1);
|
||||
public DateTime Next { get; private set; }
|
||||
public TimeSpan Delay { get; set; }
|
||||
public TimeSpan Interval { get; set; }
|
||||
public int Index { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public int RemainingCount => Count - Index;
|
||||
public bool Running { get; private set; }
|
||||
|
||||
public Timer(TimeSpan interval, int count) => Init(interval, interval, count);
|
||||
public TimerProfile GetProfile() => !Core.Profiling ? null : TimerProfile.Acquire(ToString() ?? "null");
|
||||
|
||||
public Timer(TimeSpan delay, TimeSpan interval, int count = 0) => Init(delay, interval, count);
|
||||
public override string ToString() => GetType().FullName;
|
||||
|
||||
protected void Init(TimeSpan delay, TimeSpan interval, int count)
|
||||
public Timer Start()
|
||||
{
|
||||
if (Running)
|
||||
{
|
||||
Running = false;
|
||||
Delay = delay;
|
||||
Index = 0;
|
||||
Interval = interval;
|
||||
Count = count;
|
||||
_nextTimer = null;
|
||||
_prevTimer = null;
|
||||
Next = Core.Now + Delay;
|
||||
|
||||
var prof = GetProfile();
|
||||
|
||||
if (prof != null)
|
||||
{
|
||||
prof.Created++;
|
||||
}
|
||||
}
|
||||
|
||||
protected int Version { get; set; } // Used to determine if a timer was altered and we should abandon it.
|
||||
|
||||
public DateTime Next { get; private set; }
|
||||
public TimeSpan Delay { get; set; }
|
||||
public TimeSpan Interval { get; set; }
|
||||
public int Index { get; private set; }
|
||||
public int Count { get; private set; }
|
||||
public int RemainingCount => Count - Index;
|
||||
public bool Running { get; private set; }
|
||||
|
||||
public TimerProfile GetProfile() => !Core.Profiling ? null : TimerProfile.Acquire(ToString() ?? "null");
|
||||
|
||||
public override string ToString() => GetType().FullName;
|
||||
|
||||
public Timer Start()
|
||||
{
|
||||
if (Running)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
Index = 0;
|
||||
Running = true;
|
||||
AddTimer(this, (long)Delay.TotalMilliseconds);
|
||||
|
||||
var prof = GetProfile();
|
||||
|
||||
if (prof != null)
|
||||
{
|
||||
prof.Started++;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public virtual void Stop()
|
||||
Index = 0;
|
||||
Running = true;
|
||||
AddTimer(this, (long)Delay.TotalMilliseconds);
|
||||
|
||||
var prof = GetProfile();
|
||||
|
||||
if (prof != null)
|
||||
{
|
||||
if (!Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not detach if we are in the middle of executing the timer wheel for this ring/slot
|
||||
if (!_timerWheelExecuting || _ringIndexes[_ring] != _slot)
|
||||
{
|
||||
// We are at the head
|
||||
if (_rings[_ring][_slot] == this)
|
||||
{
|
||||
_rings[_ring][_slot] = _nextTimer;
|
||||
}
|
||||
|
||||
Detach();
|
||||
}
|
||||
|
||||
Running = false;
|
||||
Version++;
|
||||
|
||||
var prof = GetProfile();
|
||||
if (prof != null)
|
||||
{
|
||||
prof.Stopped++;
|
||||
}
|
||||
prof.Started++;
|
||||
}
|
||||
|
||||
protected virtual void OnTick()
|
||||
return this;
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
if (!Running)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
private void Attach(Timer timer)
|
||||
Running = false;
|
||||
|
||||
// Do not detach if we are in the middle of executing the timer wheel for this ring/slot
|
||||
if (!_timerWheelExecuting || _ringIndexes[_ring] != _slot)
|
||||
{
|
||||
_nextTimer = timer;
|
||||
if (timer != null)
|
||||
// We are at the head
|
||||
if (_rings[_ring][_slot] == this)
|
||||
{
|
||||
timer._prevTimer = this;
|
||||
_rings[_ring][_slot] = _nextTimer;
|
||||
}
|
||||
|
||||
Detach();
|
||||
OnDetach();
|
||||
}
|
||||
|
||||
private void Detach()
|
||||
{
|
||||
if (_prevTimer != null)
|
||||
{
|
||||
_prevTimer._nextTimer = _nextTimer;
|
||||
}
|
||||
|
||||
if (_nextTimer != null)
|
||||
{
|
||||
_nextTimer._prevTimer = _prevTimer;
|
||||
}
|
||||
|
||||
_nextTimer = null;
|
||||
_prevTimer = null;
|
||||
}
|
||||
|
||||
internal virtual void OnDetach()
|
||||
Version++;
|
||||
|
||||
var prof = GetProfile();
|
||||
if (prof != null)
|
||||
{
|
||||
prof.Stopped++;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTick()
|
||||
{
|
||||
}
|
||||
|
||||
private void Attach(Timer timer)
|
||||
{
|
||||
_nextTimer = timer;
|
||||
if (timer != null)
|
||||
{
|
||||
timer._prevTimer = this;
|
||||
}
|
||||
}
|
||||
|
||||
private void Detach()
|
||||
{
|
||||
if (_prevTimer != null)
|
||||
{
|
||||
_prevTimer._nextTimer = _nextTimer;
|
||||
}
|
||||
|
||||
if (_nextTimer != null)
|
||||
{
|
||||
_nextTimer._prevTimer = _prevTimer;
|
||||
}
|
||||
|
||||
_nextTimer = null;
|
||||
_prevTimer = null;
|
||||
}
|
||||
|
||||
internal virtual void OnDetach()
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,48 +16,47 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Server
|
||||
namespace Server;
|
||||
|
||||
public struct TimerExecutionToken
|
||||
{
|
||||
public struct TimerExecutionToken
|
||||
private Timer.DelayCallTimer _timer;
|
||||
|
||||
internal TimerExecutionToken(Timer.DelayCallTimer timer) => _timer = timer;
|
||||
|
||||
public bool Running
|
||||
{
|
||||
private Timer.DelayCallTimer _timer;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.Running == true;
|
||||
}
|
||||
|
||||
internal TimerExecutionToken(Timer.DelayCallTimer timer) => _timer = timer;
|
||||
public int Index
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.Index ?? 0;
|
||||
}
|
||||
|
||||
public bool Running
|
||||
public int RemainingCount
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.RemainingCount ?? 0;
|
||||
}
|
||||
|
||||
public DateTime Next
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.Next ?? DateTime.MinValue;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.Running == true;
|
||||
_timer._returnOnDetach = true;
|
||||
_timer?.Stop();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
public int Index
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.Index ?? 0;
|
||||
}
|
||||
|
||||
public int RemainingCount
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.RemainingCount ?? 0;
|
||||
}
|
||||
|
||||
public DateTime Next
|
||||
{
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
get => _timer?.Next ?? DateTime.MinValue;
|
||||
}
|
||||
|
||||
public void Cancel()
|
||||
{
|
||||
if (_timer != null)
|
||||
{
|
||||
_timer._returnOnDetach = true;
|
||||
_timer?.Stop();
|
||||
_timer = null;
|
||||
}
|
||||
|
||||
this = default;
|
||||
}
|
||||
this = default;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ namespace Server.Mobiles
|
|||
|
||||
public override void OnAfterDelete()
|
||||
{
|
||||
base.OnAfterDelete();
|
||||
_timerToken.Cancel();
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue