servuo/Server/TimerRegistry.cs
VitaNex f840e50340 Updated .editorconfig file with advanced configuration settings that will help maintain user code style and best practices with most IDE's.
Complete core housekeeping operation using "Profile 1" with new .editorconfig settings.
2021-01-15 12:51:10 -08:00

330 lines
8.8 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Server.Commands;
namespace Server
{
public static class TimerRegistry
{
public static readonly bool Debug = false;
private const int _RegistryThreshold = 250;
public static void Initialize()
{
CommandSystem.Register("CheckTimers", AccessLevel.Administrator, e =>
{
foreach (var kvp in Timers)
{
Utility.WriteConsoleColor(ConsoleColor.Green, "Timer ID: {0}", kvp.Key);
var elements = 0;
var timerCount = kvp.Value.Count;
for (var i = 0; i < kvp.Value.Count; i++)
{
Console.WriteLine("Delay/Interval: {0}", kvp.Value[i].Interval);
Console.WriteLine("Timer Priority: {0}", kvp.Value[i].Priority);
var dic = kvp.Value[i].GetType().GetProperty("Registry").GetValue(kvp.Value[i], null) as IDictionary;
if (dic != null)
{
elements += dic.Count;
}
}
Console.WriteLine("Timers: {0}", timerCount);
Console.WriteLine("Registered elements: {0}!", elements);
}
});
}
public static Dictionary<string, List<Timer>> Timers { get; set; } = new Dictionary<string, List<Timer>>();
public static void Register<T>(string id, T instance, TimeSpan duration, Action<T> callback)
{
Register(id, instance, duration, TimeSpan.Zero, true, true, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, TimerPriority? priority, Action<T> callback)
{
Register(id, instance, duration, TimeSpan.Zero, true, true, priority, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, TimeSpan delay, Action<T> callback)
{
Register(id, instance, duration, delay, true, true, null, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, TimeSpan delay, TimerPriority? priority, Action<T> callback)
{
Register(id, instance, duration, delay, true, true, priority, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, bool removeOnExpire, Action<T> callback)
{
Register(id, instance, duration, TimeSpan.Zero, removeOnExpire, true, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, bool removeOnExpire, TimerPriority? priority, Action<T> callback)
{
Register(id, instance, duration, TimeSpan.Zero, removeOnExpire, true, priority, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, Action<T> callback)
{
Register(id, instance, duration, delay, removeOnExpire, true, null, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, TimerPriority? priority, Action<T> callback)
{
Register(id, instance, duration, delay, removeOnExpire, true, priority, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, bool checkDeleted, Action<T> callback)
{
Register(id, instance, duration, delay, removeOnExpire, true, null, callback);
}
public static void Register<T>(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, bool checkDeleted, TimerPriority? priority, Action<T> callback)
{
if (HasTimer(id, instance))
{
return;
}
var timer = GetTimer(id, instance, true);
if (Debug) Console.WriteLine("Registering: {0} - {1}...", id, instance);
if (timer == null)
{
if (Debug) Console.WriteLine("Timer not Found, creating new one...");
timer = new RegistryTimer<T>(delay == TimeSpan.Zero ? ProcessDelay(duration) : delay, callback, removeOnExpire, checkDeleted, priority);
Timers[id].Add(timer);
timer.Start();
}
else if (Debug)
{
Console.WriteLine("Timer Found, adding to existing registry...");
}
if (!timer.Registry.ContainsKey(instance))
{
if (Debug) Console.WriteLine("Adding {0} to the timer registry!", instance);
timer.Registry[instance] = Core.TickCount + (long)duration.TotalMilliseconds;
}
else if (Debug)
{
Console.WriteLine("Instnace already exists in the timer registry!");
}
}
public static void RemoveFromRegistry<T>(string id, T instance)
{
var timer = GetTimerFor(id, instance);
if (timer != null && timer.Registry.ContainsKey(instance))
{
timer.Registry.Remove(instance);
if (Debug) Console.WriteLine("Removing {0} from the registry", instance);
if (timer.Registry.Count == 0)
{
UnregisterTimer(timer);
}
}
}
public static bool UpdateRegistry<T>(string id, T instance, TimeSpan duration)
{
var timer = GetTimerFor(id, instance);
if (Debug) Console.WriteLine("Updating Registry for {0} - {1}...", id, instance);
if (timer != null)
{
if (Debug) Console.WriteLine("Complete!");
timer.Registry[instance] = Core.TickCount + (long)duration.TotalMilliseconds;
return true;
}
if (Debug)
{
Console.WriteLine("Failed, timer not found");
}
return false;
}
public static RegistryTimer<T> GetTimer<T>(string id, T instance, bool create)
{
if (Timers.ContainsKey(id))
{
return Timers[id].FirstOrDefault(t => t is RegistryTimer<T> regTimer && regTimer.Registry.Count < _RegistryThreshold) as RegistryTimer<T>;
}
if (create)
{
Timers[id] = new List<Timer>();
}
return null;
}
public static RegistryTimer<T> GetTimerFor<T>(string id, T instance)
{
if (Timers.ContainsKey(id))
{
return Timers[id].FirstOrDefault(t => t is RegistryTimer<T> timer && timer.Registry.ContainsKey(instance)) as RegistryTimer<T>;
}
return null;
}
public static bool HasTimer<T>(string id, T instance)
{
return GetTimerFor(id, instance) != null;
}
public static void UnregisterTimer(Timer timer)
{
timer.Stop();
var id = GetTimerID(timer);
if (!String.IsNullOrEmpty(id) && Timers.ContainsKey(id))
{
Timers[id].Remove(timer);
if (Timers[id].Count == 0)
{
if (Debug) Console.WriteLine("Remove {0} from the timer list", id);
Timers.Remove(id);
}
}
}
public static string GetTimerID(Timer timer)
{
foreach (var kvp in Timers)
{
if (kvp.Value.Any(t => t == timer))
{
return kvp.Key;
}
}
return String.Empty;
}
public static TimeSpan ProcessDelay(TimeSpan duration)
{
var seconds = duration.TotalSeconds;
if (seconds >= 86400) // 1 day
{
return TimeSpan.FromMinutes(5);
}
if (seconds >= 3600) // 1 hour
{
return TimeSpan.FromMinutes(1);
}
if (seconds >= 600) // 10 minutes
{
return TimeSpan.FromSeconds(1);
}
var mils = duration.TotalMilliseconds;
if (mils < 10)
{
return TimeSpan.Zero;
}
if (mils < 250)
{
return TimeSpan.FromMilliseconds(10);
}
if (mils < 500)
{
return TimeSpan.FromMilliseconds(250);
}
return TimeSpan.FromMilliseconds(500);
}
}
public class RegistryTimer<T> : Timer
{
public Dictionary<T, long> Registry { get; set; } = new Dictionary<T, long>();
public Action<T> Callback { get; set; }
public bool RemoveOnExpire { get; set; }
public bool CheckDeleted { get; set; }
public RegistryTimer(TimeSpan delay, Action<T> callback, bool removeOnExpire, bool checkDeleted, TimerPriority? priority)
: base(delay, delay)
{
Callback = callback;
RemoveOnExpire = removeOnExpire;
CheckDeleted = checkDeleted;
if (priority != null)
{
Priority = (TimerPriority)priority;
}
}
protected override void OnTick()
{
var instances = Registry.Keys.Where(v => Registry[v] <= Core.TickCount || (CheckDeleted && v is IEntity e && e.Deleted)).ToList();
for (var i = 0; i < instances.Count; i++)
{
var instance = instances[i];
if (IsDeleted(instance))
{
if (TimerRegistry.Debug) Console.WriteLine("Removing from Registry [Deleted]: {0}", instance);
Registry.Remove(instance);
}
else
{
if (Callback != null)
{
Callback(instance);
}
if (IsExpired(instance))
{
if (TimerRegistry.Debug) Console.WriteLine("Removing from Registry [Processed]: {0}", instance);
Registry.Remove(instance);
}
}
}
ColUtility.Free(instances);
if (Registry.Count == 0)
{
TimerRegistry.UnregisterTimer(this);
}
}
private bool IsExpired(T instance)
{
return RemoveOnExpire && (!Registry.ContainsKey(instance) || Registry[instance] <= Core.TickCount);
}
private bool IsDeleted(T instance)
{
return CheckDeleted && instance is IEntity e && e.Deleted;
}
}
}