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> Timers { get; set; } = new Dictionary>(); public static void Register(string id, T instance, TimeSpan duration, Action callback) { Register(id, instance, duration, TimeSpan.Zero, true, true, callback); } public static void Register(string id, T instance, TimeSpan duration, TimerPriority? priority, Action callback) { Register(id, instance, duration, TimeSpan.Zero, true, true, priority, callback); } public static void Register(string id, T instance, TimeSpan duration, TimeSpan delay, Action callback) { Register(id, instance, duration, delay, true, true, null, callback); } public static void Register(string id, T instance, TimeSpan duration, TimeSpan delay, TimerPriority? priority, Action callback) { Register(id, instance, duration, delay, true, true, priority, callback); } public static void Register(string id, T instance, TimeSpan duration, bool removeOnExpire, Action callback) { Register(id, instance, duration, TimeSpan.Zero, removeOnExpire, true, callback); } public static void Register(string id, T instance, TimeSpan duration, bool removeOnExpire, TimerPriority? priority, Action callback) { Register(id, instance, duration, TimeSpan.Zero, removeOnExpire, true, priority, callback); } public static void Register(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, Action callback) { Register(id, instance, duration, delay, removeOnExpire, true, null, callback); } public static void Register(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, TimerPriority? priority, Action callback) { Register(id, instance, duration, delay, removeOnExpire, true, priority, callback); } public static void Register(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, bool checkDeleted, Action callback) { Register(id, instance, duration, delay, removeOnExpire, true, null, callback); } public static void Register(string id, T instance, TimeSpan duration, TimeSpan delay, bool removeOnExpire, bool checkDeleted, TimerPriority? priority, Action 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(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(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(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 GetTimer(string id, T instance, bool create) { if (Timers.ContainsKey(id)) { return Timers[id].FirstOrDefault(t => t is RegistryTimer regTimer && regTimer.Registry.Count < _RegistryThreshold) as RegistryTimer; } if (create) { Timers[id] = new List(); } return null; } public static RegistryTimer GetTimerFor(string id, T instance) { if (Timers.ContainsKey(id)) { return Timers[id].FirstOrDefault(t => t is RegistryTimer timer && timer.Registry.ContainsKey(instance)) as RegistryTimer; } return null; } public static bool HasTimer(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 : Timer { public Dictionary Registry { get; set; } = new Dictionary(); public Action Callback { get; set; } public bool RemoveOnExpire { get; set; } public bool CheckDeleted { get; set; } public RegistryTimer(TimeSpan delay, Action 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; } } }