using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; namespace Unity.MLAgents.Actuators { /// /// ActionSegment{T} is a data structure that allows access to a segment of an underlying array /// in order to avoid the copying and allocation of sub-arrays. The segment is defined by /// the offset into the original array, and an length. /// /// The type of object stored in the underlying public readonly struct ActionSegment : IEnumerable, IEquatable> where T : struct { /// /// The zero-based offset into the original array at which this segment starts. /// public readonly int Offset; /// /// The number of items this segment can access in the underlying array. /// public readonly int Length; /// /// An Empty segment which has an offset of 0, a Length of 0, and it's underlying array /// is also empty. /// public static ActionSegment Empty = new ActionSegment(System.Array.Empty(), 0, 0); static void CheckParameters(IReadOnlyCollection actionArray, int offset, int length) { #if DEBUG if (offset + length > actionArray.Count) { throw new ArgumentOutOfRangeException(nameof(offset), $"Arguments offset: {offset} and length: {length} " + $"are out of bounds of actionArray: {actionArray.Count}."); } #endif } /// /// Construct an with just an actionArray. The will /// be set to 0 and the will be set to `actionArray.Length`. /// /// The action array to use for the this segment. public ActionSegment(T[] actionArray) : this(actionArray ?? System.Array.Empty(), 0, actionArray?.Length ?? 0) { } /// /// Construct an with an underlying array /// and offset, and a length. /// /// The underlying array which this segment has a view into /// The zero-based offset into the underlying array. /// The length of the segment. public ActionSegment(T[] actionArray, int offset, int length) { #if DEBUG CheckParameters(actionArray ?? System.Array.Empty(), offset, length); #endif Array = actionArray ?? System.Array.Empty(); Offset = offset; Length = length; } /// /// Get the underlying of this segment. /// public T[] Array { get; } /// /// Allows access to the underlying array using array syntax. /// /// The zero-based index of the segment. /// Thrown when the index is less than 0 or /// greater than or equal to public T this[int index] { get { if (index < 0 || index > Length) { throw new IndexOutOfRangeException($"Index out of bounds, expected a number between 0 and {Length}"); } return Array[Offset + index]; } set { if (index < 0 || index > Length) { throw new IndexOutOfRangeException($"Index out of bounds, expected a number between 0 and {Length}"); } Array[Offset + index] = value; } } /// /// Sets the segment of the backing array to all zeros. /// public void Clear() { System.Array.Clear(Array, Offset, Length); } /// /// Check if the segment is empty. /// public bool IsEmpty() { return Array == null || Array.Length == 0; } /// IEnumerator IEnumerable.GetEnumerator() { return new Enumerator(this); } /// public IEnumerator GetEnumerator() { return new Enumerator(this); } /// public override bool Equals(object obj) { if (!(obj is ActionSegment)) { return false; } return Equals((ActionSegment)obj); } /// public bool Equals(ActionSegment other) { return Offset == other.Offset && Length == other.Length && Array.SequenceEqual(other.Array); } /// public override int GetHashCode() { unchecked { var hashCode = Offset; hashCode = (hashCode * 397) ^ Length; hashCode = (hashCode * 397) ^ (Array != null ? Array.GetHashCode() : 0); return hashCode; } } /// /// A private for the value type which follows its /// rules of being a view into an underlying . /// struct Enumerator : IEnumerator { readonly T[] m_Array; readonly int m_Start; readonly int m_End; // cache Offset + Count, since it's a little slow int m_Current; internal Enumerator(ActionSegment arraySegment) { Debug.Assert(arraySegment.Array != null); Debug.Assert(arraySegment.Offset >= 0); Debug.Assert(arraySegment.Length >= 0); Debug.Assert(arraySegment.Offset + arraySegment.Length <= arraySegment.Array.Length); m_Array = arraySegment.Array; m_Start = arraySegment.Offset; m_End = arraySegment.Offset + arraySegment.Length; m_Current = arraySegment.Offset - 1; } public bool MoveNext() { if (m_Current < m_End) { m_Current++; return m_Current < m_End; } return false; } public T Current { get { if (m_Current < m_Start) throw new InvalidOperationException("Enumerator not started."); if (m_Current >= m_End) throw new InvalidOperationException("Enumerator has reached the end already."); return m_Array[m_Current]; } } object IEnumerator.Current => Current; void IEnumerator.Reset() { m_Current = m_Start - 1; } public void Dispose() { } } } }