//
// 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;
}
}