using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Runtime.InteropServices; using CustomCollections; namespace Decal.Adapter.IDQueue; /// /// A scheduler: callers request actions. Multiple callers can request the same action. /// -Each caller gets one action, then the other callers get a turn. /// -If a new caller arrives, it is given priority in the turns list. /// -Everytime a caller gets a turn, it selects its next available action in round-robin fashion. /// -If all of a caller's actions are unavailable when it is that caller's turn, its turn is lost and it must wait in line again. /// -If no caller can take a turn, the null action is returned. /// Thus if multiple callers request the same action, it will be tried more often /// since the attempt will occur during the turns of multiple callers. /// /// /// [CLSCompliant(true)] [ClassInterface(ClassInterfaceType.None)] public abstract class FairRoundRobinScheduleQueue { public class ActionRemovedEventArgs : EventArgs { private ACTIONTYPE mAction; public ACTIONTYPE Action => mAction; internal ActionRemovedEventArgs(ACTIONTYPE pAction) { mAction = pAction; } } private class ActionInfo : IEquatable { public ACTIONTYPE Action; public DateTime Expires; public int TryCount; public ActionInfo(ACTIONTYPE pAction, DateTime pExpires) { Action = pAction; Expires = pExpires; } public bool IsExpired() { return Expires < DateTime.Now; } public bool Equals(ActionInfo other) { ref ACTIONTYPE action = ref Action; object obj = other.Action; return action.Equals(obj); } public override bool Equals(object obj) { return Equals(obj as ActionInfo); } public override int GetHashCode() { return Action.GetHashCode(); } } private HashedList Callers = new HashedList(); private Dictionary ActInfos = new Dictionary(); private Dictionary> CallerActions = new Dictionary>(); private Dictionary> ActionCallers = new Dictionary>(); private int MaximumTryCount; private ACTIONTYPE NullAction; public int CallerCount => Callers.Count; public int ActionCount => ActInfos.Count; public event EventHandler OnActionRemoved; /// /// /// /// The most times an action can be attempted before it fails. /// An ACTIONTYPE to return when no action is available. protected FairRoundRobinScheduleQueue(int pMaximumTryCount, ACTIONTYPE pNullAction) { MaximumTryCount = pMaximumTryCount; NullAction = pNullAction; } protected abstract bool IsActionValidNow(ACTIONTYPE action); protected abstract bool IsActionPermanentlyInvalid(ACTIONTYPE action); private void RotateQueue(HashedList q) { if (q.Count > 0) { T item = q[0]; q.RemoveAt(0); q.Add(item); } } public void ClearAll() { Callers.Clear(); ActInfos.Clear(); CallerActions.Clear(); ActionCallers.Clear(); } public void DeleteAction(ACTIONTYPE action) { if (!ActionCallers.ContainsKey(action) || !ActInfos.ContainsKey(action)) { return; } ActionInfo item = ActInfos[action]; ActInfos.Remove(action); HashedList hashedList = ActionCallers[action]; ActionCallers.Remove(action); foreach (CALLERTYPE item2 in hashedList) { CallerActions[item2].Remove(item); if (CallerActions[item2].Count == 0) { CallerActions.Remove(item2); Callers.Remove(item2); } } if (this.OnActionRemoved != null) { this.OnActionRemoved(this, new ActionRemovedEventArgs(action)); } } public void DeleteCaller(CALLERTYPE caller) { Callers.Remove(caller); if (!CallerActions.ContainsKey(caller)) { return; } HashedList hashedList = CallerActions[caller]; CallerActions.Remove(caller); HashedList hashedList2 = new HashedList(); foreach (ActionInfo item in hashedList) { if (ActionCallers.ContainsKey(item.Action)) { ActionCallers[item.Action].Remove(caller); if (ActionCallers[item.Action].Count == 0) { ActionCallers.Remove(item.Action); ActInfos.Remove(item.Action); hashedList2.Add(item); } } } if (this.OnActionRemoved == null) { return; } foreach (ActionInfo item2 in hashedList2) { this.OnActionRemoved(this, new ActionRemovedEventArgs(item2.Action)); } } public ReadOnlyCollection GetActionsForCaller(CALLERTYPE caller) { List list = new List(); if (CallerActions.ContainsKey(caller)) { foreach (ActionInfo item in CallerActions[caller]) { list.Add(item.Action); } } return list.AsReadOnly(); } public ReadOnlyCollection GetCallersForAction(ACTIONTYPE action) { if (ActionCallers.ContainsKey(action)) { return ActionCallers[action].AsReadOnly(); } return new List().AsReadOnly(); } public void Enqueue(CALLERTYPE caller, ACTIONTYPE action, DateTime expiration) { ActionInfo actionInfo; if (ActInfos.ContainsKey(action)) { actionInfo = ActInfos[action]; if (actionInfo.Expires < expiration) { actionInfo.Expires = expiration; } actionInfo.TryCount = 0; } else { actionInfo = new ActionInfo(action, expiration); ActInfos[action] = actionInfo; } if (!CallerActions.ContainsKey(caller) || !CallerActions[caller].Contains(actionInfo)) { if (!Callers.Contains(caller)) { Callers.Insert(0, caller); } if (!CallerActions.ContainsKey(caller)) { CallerActions.Add(caller, new HashedList()); } CallerActions[caller].Add(actionInfo); if (!ActionCallers.ContainsKey(action)) { ActionCallers.Add(action, new HashedList()); } ActionCallers[action].Add(caller); } } public ACTIONTYPE GetNextAction(ref CALLERTYPE requester) { HashedList hashedList = new HashedList(); ACTIONTYPE result = NullAction; for (int i = 0; i < Callers.Count; i++) { CALLERTYPE val = Callers[0]; HashedList hashedList2 = CallerActions[val]; bool flag = false; for (int j = 0; j < hashedList2.Count; j++) { ActionInfo actionInfo = hashedList2[0]; if (hashedList.Contains(actionInfo)) { RotateQueue(hashedList2); continue; } if (actionInfo.IsExpired()) { hashedList.Add(actionInfo); RotateQueue(hashedList2); continue; } if (IsActionPermanentlyInvalid(actionInfo.Action)) { hashedList.Add(actionInfo); RotateQueue(hashedList2); continue; } if (!IsActionValidNow(actionInfo.Action)) { RotateQueue(hashedList2); continue; } flag = true; result = actionInfo.Action; requester = val; RotateQueue(hashedList2); actionInfo.TryCount++; if (actionInfo.TryCount > MaximumTryCount) { hashedList.Add(actionInfo); } break; } RotateQueue(Callers); if (flag) { break; } } foreach (ActionInfo item in hashedList) { DeleteAction(item.Action); } return result; } }