//Timers/DecayingAlarm.cs |
@@ -0,0 +1,143 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
using System; |
using System.Collections.Generic; |
using System.Diagnostics; |
using System.Linq; |
using System.Threading; |
using System.Xml.Serialization; |
|
namespace wasSharp.Timers |
{ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
/// <summary> |
/// An alarm class similar to the UNIX alarm with the added benefit |
/// of a decaying timer that tracks the time between rescheduling. |
/// </summary> |
/// <remarks> |
/// (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 |
/// </remarks> |
public class DecayingAlarm : IDisposable |
{ |
[Flags] |
public enum DECAY_TYPE |
{ |
[Reflection.NameAttribute("none")] [XmlEnum(Name = "none")] NONE = 0, |
[Reflection.NameAttribute("arithmetic")] [XmlEnum(Name = "arithmetic")] ARITHMETIC = 1, |
[Reflection.NameAttribute("geometric")] [XmlEnum(Name = "geometric")] GEOMETRIC = 2, |
[Reflection.NameAttribute("harmonic")] [XmlEnum(Name = "harmonic")] HARMONIC = 4, |
[Reflection.NameAttribute("weighted")] [XmlEnum(Name = "weighted")] WEIGHTED = 5 |
} |
|
private readonly DECAY_TYPE decay = DECAY_TYPE.NONE; |
private readonly Stopwatch elapsed = new Stopwatch(); |
private readonly object LockObject = new object(); |
private readonly HashSet<double> times = new HashSet<double>(); |
private Timer alarm; |
|
/// <summary> |
/// The default constructor using no decay. |
/// </summary> |
public DecayingAlarm() |
{ |
Signal = new ManualResetEvent(false); |
} |
|
/// <summary> |
/// The constructor for the DecayingAlarm class taking as parameter a decay type. |
/// </summary> |
/// <param name="decay">the type of decay: arithmetic, geometric, harmonic, heronian or quadratic</param> |
public DecayingAlarm(DECAY_TYPE decay) |
{ |
Signal = new ManualResetEvent(false); |
this.decay = decay; |
} |
|
public ManualResetEvent Signal { get; set; } |
|
public void Dispose() |
{ |
Dispose(true); |
GC.SuppressFinalize(this); |
} |
|
~DecayingAlarm() |
{ |
Dispose(false); |
} |
|
public void Alarm(double deadline) |
{ |
lock (LockObject) |
{ |
switch (alarm == null) |
{ |
case true: |
elapsed.Start(); |
alarm = new Timer(o => |
{ |
lock (LockObject) |
{ |
Signal.Set(); |
elapsed.Stop(); |
times.Clear(); |
alarm.Dispose(); |
alarm = null; |
} |
}, null, (int) deadline, 0); |
return; |
case false: |
elapsed.Stop(); |
times.Add(elapsed.ElapsedMilliseconds); |
switch (decay) |
{ |
case DECAY_TYPE.ARITHMETIC: |
alarm?.Change( |
(int) ((deadline + times.Aggregate((a, b) => b + a))/(1f + times.Count)), 0); |
break; |
case DECAY_TYPE.GEOMETRIC: |
alarm?.Change((int) Math.Pow(deadline*times.Aggregate((a, b) => b*a), |
1f/(1f + times.Count)), 0); |
break; |
case DECAY_TYPE.HARMONIC: |
alarm?.Change((int) ((1f + times.Count)/ |
(1f/deadline + times.Aggregate((a, b) => 1f/b + 1f/a))), 0); |
break; |
case DECAY_TYPE.WEIGHTED: |
var d = new HashSet<double>(times) {deadline}; |
var total = d.Aggregate((a, b) => b + a); |
alarm?.Change( |
(int) d.Aggregate((a, b) => Math.Pow(a, 2)/total + Math.Pow(b, 2)/total), 0); |
break; |
default: |
alarm?.Change((int) deadline, 0); |
break; |
} |
elapsed.Reset(); |
elapsed.Start(); |
break; |
} |
} |
} |
|
private void Dispose(bool dispose) |
{ |
if (alarm != null) |
{ |
alarm.Dispose(); |
alarm = null; |
} |
} |
|
public DecayingAlarm Clone() |
{ |
return new DecayingAlarm(decay); |
} |
} |
} |
//Timers/TimedThrottle.cs |
@@ -0,0 +1,73 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
using System; |
|
namespace wasSharp.Timers |
{ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2015 - License: GNU GPLv3 // |
/////////////////////////////////////////////////////////////////////////// |
/// <summary> |
/// Given a number of allowed events per seconds, this class allows you |
/// to determine via the IsSafe property whether it is safe to trigger |
/// another lined-up event. This is mostly used to check that throttles |
/// are being respected. |
/// </summary> |
public class TimedThrottle : IDisposable |
{ |
private readonly uint EventsAllowed; |
private readonly object LockObject = new object(); |
private Timer timer; |
public uint TriggeredEvents; |
|
public TimedThrottle(uint events, uint seconds) |
{ |
EventsAllowed = events; |
if (timer == null) |
{ |
timer = new Timer(o => |
{ |
lock (LockObject) |
{ |
TriggeredEvents = 0; |
} |
}, null, (int) seconds, (int) seconds); |
} |
} |
|
public bool IsSafe |
{ |
get |
{ |
lock (LockObject) |
{ |
return ++TriggeredEvents <= EventsAllowed; |
} |
} |
} |
|
public void Dispose() |
{ |
Dispose(true); |
GC.SuppressFinalize(this); |
} |
|
~TimedThrottle() |
{ |
Dispose(false); |
} |
|
private void Dispose(bool dispose) |
{ |
if (timer != null) |
{ |
timer.Dispose(); |
timer = null; |
} |
} |
} |
} |
//Timers/Timer.cs |
@@ -0,0 +1,114 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
using System; |
using System.Threading; |
using System.Threading.Tasks; |
|
namespace wasSharp.Timers |
{ |
public delegate void TimerCallback(object state); |
|
public class Timer : IDisposable |
{ |
private static readonly Task CompletedTask = Task.FromResult(false); |
private readonly TimerCallback Callback; |
private readonly object State; |
private Task Delay; |
private bool Disposed; |
private int Period; |
private CancellationTokenSource TokenSource; |
|
public Timer(TimerCallback callback, object state, int dueTime, int period) |
{ |
Callback = callback; |
State = state; |
Period = period; |
Reset(dueTime); |
} |
|
public Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period) |
: this(callback, state, (int) dueTime.TotalMilliseconds, (int) period.TotalMilliseconds) |
{ |
} |
|
public Timer(TimerCallback callback) : this(callback, null, TimeSpan.Zero, TimeSpan.Zero) |
{ |
} |
|
public void Dispose() |
{ |
Dispose(true); |
GC.SuppressFinalize(this); |
} |
|
~Timer() |
{ |
Dispose(false); |
} |
|
private void Dispose(bool cleanUpManagedObjects) |
{ |
if (cleanUpManagedObjects) |
Cancel(); |
Disposed = true; |
} |
|
public void Change(int dueTime, int period) |
{ |
Period = period; |
Reset(dueTime); |
} |
|
public void Change(uint dueTime, int period) |
{ |
Period = period; |
Change((int) dueTime, period); |
} |
|
public void Change(TimeSpan dueTime, TimeSpan period) |
{ |
Change((int) dueTime.TotalMilliseconds, (int) period.TotalMilliseconds); |
} |
|
private void Reset(int due) |
{ |
Cancel(); |
if (due <= 0) |
return; |
TokenSource = new CancellationTokenSource(); |
Action tick = null; |
tick = () => |
{ |
Task.Run(() => Callback(State)); |
if (Disposed) |
return; |
Delay = Period > 0 ? Task.Delay(Period, TokenSource.Token) : CompletedTask; |
if (Delay.IsCompleted) |
return; |
Delay.ContinueWith(t => tick(), TokenSource.Token); |
}; |
Delay = due > 0 ? Task.Delay(due, TokenSource.Token) : CompletedTask; |
if (Delay.IsCompleted) |
return; |
Delay.ContinueWith(t => tick(), TokenSource.Token); |
} |
|
public void Stop() |
{ |
Change(0, 0); |
} |
|
private void Cancel() |
{ |
if (TokenSource == null) |
return; |
TokenSource.Cancel(); |
TokenSource.Dispose(); |
TokenSource = null; |
} |
} |
} |
//Timers/Utilities/Extensions.cs |
@@ -0,0 +1,35 @@ |
/////////////////////////////////////////////////////////////////////////// |
// Copyright (C) Wizardry and Steamworks 2013 - License: GNU GPLv3 // |
// Please see: http://www.gnu.org/licenses/gpl.html for legal details, // |
// rights of fair usage, the disclaimer and warranty conditions. // |
/////////////////////////////////////////////////////////////////////////// |
|
using System; |
|
namespace wasSharp.Timers.Utilities |
{ |
public static class Extensions |
{ |
/// <summary> |
/// Convert an Unix timestamp to a DateTime structure. |
/// </summary> |
/// <param name="unixTimestamp">the Unix timestamp to convert</param> |
/// <returns>the DateTime structure</returns> |
/// <remarks>the function assumes UTC time</remarks> |
public static DateTime UnixTimestampToDateTime(this uint unixTimestamp) |
{ |
return new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc).AddSeconds(unixTimestamp).ToUniversalTime(); |
} |
|
/// <summary> |
/// Convert a DateTime structure to a Unix timestamp. |
/// </summary> |
/// <param name="dateTime">the DateTime structure to convert</param> |
/// <returns>the Unix timestamp</returns> |
/// <remarks>the function assumes UTC time</remarks> |
public static uint DateTimeToUnixTimestamp(this DateTime dateTime) |
{ |
return (uint) (DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc)).TotalSeconds; |
} |
} |
} |