// // Copyright (c) 2010-2024 Antmicro // // This file is licensed under the MIT License. // Full license text is available in 'licenses/MIT.txt'. // using System.Linq; using System.Collections; using System.Collections.Generic; using System.Threading; namespace Antmicro.Renode.Time { /// /// Represents a collection of time handles and allows to iterate over it in an optimal way. /// /// /// It keeps track of handles not ready for a new time grant in a separate list so they can be accessed faster. /// If there are any not ready time handles it will iterate only over them. /// public sealed class HandlesCollection : IEnumerable { /// /// Creates new empty collection. /// public HandlesCollection() { ready = new LinkedList(); notReady = new LinkedList(); locker = new object(); } /// /// Executes `Latch` method on all handles and removes the disposed ones from the collection. /// public void LatchAllAndCollectGarbage() { var wasLocked = false; try { InnerLatchAndCollectGarbage(ref wasLocked, notReady); InnerLatchAndCollectGarbage(ref wasLocked, ready); } finally { if(wasLocked) { Monitor.Exit(locker); } } } /// /// Executes `Unlatch` method on all handles. /// public void UnlatchAll() { foreach(var h in All) { h.Unlatch(); } // After unlatch some handles might be disabled. // Disabling not ready handles make them ready. // We need to update list to reflect changes. var currentNode = notReady.First; while(currentNode != null) { var next = currentNode.Next; UpdateHandle(currentNode); currentNode = next; } } /// /// Adds new handle to the collection. /// /// /// Depending of the value of , the handle is put either in or queue. /// public void Add(TimeHandle handle) { lock(locker) { (handle.IsReadyForNewTimeGrant ? ready : notReady).AddLast(handle); handle.IsDone = handle.IsReadyForNewTimeGrant; } } /// /// Updates state of a the handle by moving it to a proper queue. /// public void UpdateHandle(LinkedListNode handle) { var isOnReadyList = handle.List == ready; if((handle.Value.IsReadyForNewTimeGrant && isOnReadyList) || (!handle.Value.IsReadyForNewTimeGrant && !isOnReadyList)) { return; } lock(locker) { if(isOnReadyList) { ready.Remove(handle); notReady.AddLast(handle); handle.Value.IsDone = false; } else { notReady.Remove(handle); ready.AddLast(handle); handle.Value.IsDone = true; } } } /// /// Returns an enumerator over the part of handles collection - either not-ready-for-a-new-time-grant handles (if any) or ready ones (otherwise). /// public IEnumerator GetEnumerator() { return WithLinkedListNode.Select(x => x.Value).GetEnumerator(); } /// /// Calculates a base elapsed virtual time (minimal) for all handles. /// Returns `false` if the handles collection is empty. /// public bool TryGetCommonElapsedTime(out TimeInterval commonElapsedTime) { lock(locker) { if(notReady.Count == 0 && ready.Count == 0) { commonElapsedTime = TimeInterval.Empty; return false; } commonElapsedTime = All.Min(x => x.TotalElapsedTime); return true; } } /// /// 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 . /// public IEnumerable> WithLinkedListNode { get { var currentNode = (AreAllReadyForNewGrant ? ready : notReady).First; while(currentNode != null) { var next = currentNode.Next; yield return currentNode; currentNode = next; } } } /// /// Returns true if all handles in collection are in ready-for-a-new-grant state. /// public bool AreAllReadyForNewGrant { get { return notReady.Count == 0; } } /// /// Returns the number of handles that would be enumerated by , i.e., either not-ready-for-a-new-grant (if any) or ready-for-a-new-grant (otherwise). /// public int ActiveCount { get { return AreAllReadyForNewGrant ? ready.Count : notReady.Count; } } /// /// Returns an enumeration of all handles in the collection - not-ready queue concatenated with ready one. /// public IEnumerable All { get { return notReady.Concat(ready); } } /// IEnumerator IEnumerable.GetEnumerator() { return this.GetEnumerator(); } private void InnerLatchAndCollectGarbage(ref bool wasLocked, LinkedList list) { var current = list.First; while(current != null) { var next = current.Next; current.Value.Latch(); if(current.Value.DetachRequested) { if(!wasLocked) { Monitor.Enter(locker, ref wasLocked); } list.Remove(current); current.Value.Unlatch(); } current = next; } } private readonly LinkedList ready; private readonly LinkedList notReady; private readonly object locker; } }