1 // 2 // Copyright (c) 2010-2024 Antmicro 3 // 4 // This file is licensed under the MIT License. 5 // Full license text is available in 'licenses/MIT.txt'. 6 // 7 using System.Linq; 8 using System.Collections; 9 using System.Collections.Generic; 10 using System.Threading; 11 12 namespace Antmicro.Renode.Time 13 { 14 /// <summary> 15 /// Represents a collection of time handles and allows to iterate over it in an optimal way. 16 /// </summary> 17 /// <remarks> 18 /// It keeps track of handles not ready for a new time grant in a separate list so they can be accessed faster. 19 /// If there are any not ready time handles it will iterate only over them. 20 /// </remarks> 21 public sealed class HandlesCollection : IEnumerable<TimeHandle> 22 { 23 /// <summary> 24 /// Creates new empty collection. 25 /// </summary> HandlesCollection()26 public HandlesCollection() 27 { 28 ready = new LinkedList<TimeHandle>(); 29 notReady = new LinkedList<TimeHandle>(); 30 locker = new object(); 31 } 32 33 /// <summary> 34 /// Executes `Latch` method on all handles and removes the disposed ones from the collection. 35 /// </summary> LatchAllAndCollectGarbage()36 public void LatchAllAndCollectGarbage() 37 { 38 var wasLocked = false; 39 try 40 { 41 InnerLatchAndCollectGarbage(ref wasLocked, notReady); 42 InnerLatchAndCollectGarbage(ref wasLocked, ready); 43 } 44 finally 45 { 46 if(wasLocked) 47 { 48 Monitor.Exit(locker); 49 } 50 } 51 } 52 53 /// <summary> 54 /// Executes `Unlatch` method on all handles. 55 /// </summary> UnlatchAll()56 public void UnlatchAll() 57 { 58 foreach(var h in All) 59 { 60 h.Unlatch(); 61 } 62 63 // After unlatch some handles might be disabled. 64 // Disabling not ready handles make them ready. 65 // We need to update list to reflect changes. 66 var currentNode = notReady.First; 67 while(currentNode != null) 68 { 69 var next = currentNode.Next; 70 UpdateHandle(currentNode); 71 currentNode = next; 72 } 73 } 74 75 /// <summary> 76 /// Adds new handle to the collection. 77 /// </summary> 78 /// <remarks> 79 /// Depending of the value of <see cref="IsReadyForNewTimeGrant">, the handle is put either in <see cref="ready"> or <see cref="notReady"> queue. 80 /// </remarks> Add(TimeHandle handle)81 public void Add(TimeHandle handle) 82 { 83 lock(locker) 84 { 85 (handle.IsReadyForNewTimeGrant ? ready : notReady).AddLast(handle); 86 handle.IsDone = handle.IsReadyForNewTimeGrant; 87 } 88 } 89 90 /// <summary> 91 /// Updates state of a the handle by moving it to a proper queue. 92 /// </summary> UpdateHandle(LinkedListNode<TimeHandle> handle)93 public void UpdateHandle(LinkedListNode<TimeHandle> handle) 94 { 95 var isOnReadyList = handle.List == ready; 96 if((handle.Value.IsReadyForNewTimeGrant && isOnReadyList) 97 || (!handle.Value.IsReadyForNewTimeGrant && !isOnReadyList)) 98 { 99 return; 100 } 101 102 lock(locker) 103 { 104 if(isOnReadyList) 105 { 106 ready.Remove(handle); 107 notReady.AddLast(handle); 108 handle.Value.IsDone = false; 109 } 110 else 111 { 112 notReady.Remove(handle); 113 ready.AddLast(handle); 114 handle.Value.IsDone = true; 115 } 116 } 117 } 118 119 /// <summary> 120 /// Returns an enumerator over the part of handles collection - either not-ready-for-a-new-time-grant handles (if any) or ready ones (otherwise). 121 /// </summary> GetEnumerator()122 public IEnumerator<TimeHandle> GetEnumerator() 123 { 124 return WithLinkedListNode.Select(x => x.Value).GetEnumerator(); 125 } 126 127 /// <summary> 128 /// Calculates a base elapsed virtual time (minimal) for all handles. 129 /// Returns `false` if the handles collection is empty. 130 /// </summary> TryGetCommonElapsedTime(out TimeInterval commonElapsedTime)131 public bool TryGetCommonElapsedTime(out TimeInterval commonElapsedTime) 132 { 133 lock(locker) 134 { 135 if(notReady.Count == 0 && ready.Count == 0) 136 { 137 commonElapsedTime = TimeInterval.Empty; 138 return false; 139 } 140 commonElapsedTime = All.Min(x => x.TotalElapsedTime); 141 return true; 142 } 143 } 144 145 /// <summary> 146 /// Gets an enumerator over the part of handles collection - either not-ready-for-a-new-time-grant handles (if any) or ready ones (otherwise) returning objects of <see cref="LinkedListNode{TimeHandle}"/>. 147 /// </summary> 148 public IEnumerable<LinkedListNode<TimeHandle>> WithLinkedListNode 149 { 150 get 151 { 152 var currentNode = (AreAllReadyForNewGrant ? ready : notReady).First; 153 while(currentNode != null) 154 { 155 var next = currentNode.Next; 156 yield return currentNode; 157 currentNode = next; 158 } 159 } 160 } 161 162 /// <summary> 163 /// Returns true if all handles in collection are in ready-for-a-new-grant state. 164 /// </summary> 165 public bool AreAllReadyForNewGrant { get { return notReady.Count == 0; } } 166 167 /// <summary> 168 /// Returns the number of handles that would be enumerated by <see cref="GetEnumerator">, i.e., either not-ready-for-a-new-grant (if any) or ready-for-a-new-grant (otherwise). 169 /// </summary> 170 public int ActiveCount { get { return AreAllReadyForNewGrant ? ready.Count : notReady.Count; } } 171 172 /// <summary> 173 /// Returns an enumeration of all handles in the collection - not-ready queue concatenated with ready one. 174 /// </summary> 175 public IEnumerable<TimeHandle> All { get { return notReady.Concat(ready); } } 176 177 /// <see cref="GetEnumerator"> IEnumerable.GetEnumerator()178 IEnumerator IEnumerable.GetEnumerator() 179 { 180 return this.GetEnumerator(); 181 } 182 InnerLatchAndCollectGarbage(ref bool wasLocked, LinkedList<TimeHandle> list)183 private void InnerLatchAndCollectGarbage(ref bool wasLocked, LinkedList<TimeHandle> list) 184 { 185 var current = list.First; 186 while(current != null) 187 { 188 var next = current.Next; 189 current.Value.Latch(); 190 191 if(current.Value.DetachRequested) 192 { 193 if(!wasLocked) 194 { 195 Monitor.Enter(locker, ref wasLocked); 196 } 197 list.Remove(current); 198 current.Value.Unlatch(); 199 } 200 201 current = next; 202 } 203 } 204 205 private readonly LinkedList<TimeHandle> ready; 206 private readonly LinkedList<TimeHandle> notReady; 207 private readonly object locker; 208 } 209 } 210