| using System; | |
| using System.Collections; | |
| using System.Collections.Generic; | |
| using System.Diagnostics; | |
| using System.Linq; | |
| namespace Unity.MLAgents.Actuators | |
| { | |
| /// <summary> | |
| /// 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. | |
| /// </summary> | |
| /// <typeparam name="T">The type of object stored in the underlying <see cref="Array"/></typeparam> | |
| public readonly struct ActionSegment<T> : IEnumerable<T>, IEquatable<ActionSegment<T>> | |
| where T : struct | |
| { | |
| /// <summary> | |
| /// The zero-based offset into the original array at which this segment starts. | |
| /// </summary> | |
| public readonly int Offset; | |
| /// <summary> | |
| /// The number of items this segment can access in the underlying array. | |
| /// </summary> | |
| public readonly int Length; | |
| /// <summary> | |
| /// An Empty segment which has an offset of 0, a Length of 0, and it's underlying array | |
| /// is also empty. | |
| /// </summary> | |
| public static ActionSegment<T> Empty = new ActionSegment<T>(System.Array.Empty<T>(), 0, 0); | |
| static void CheckParameters(IReadOnlyCollection<T> actionArray, int offset, int length) | |
| { | |
| if (offset + length > actionArray.Count) | |
| { | |
| throw new ArgumentOutOfRangeException(nameof(offset), | |
| $"Arguments offset: {offset} and length: {length} " + | |
| $"are out of bounds of actionArray: {actionArray.Count}."); | |
| } | |
| } | |
| /// <summary> | |
| /// Construct an <see cref="ActionSegment{T}"/> with just an actionArray. The <see cref="Offset"/> will | |
| /// be set to 0 and the <see cref="Length"/> will be set to `actionArray.Length`. | |
| /// </summary> | |
| /// <param name="actionArray">The action array to use for the this segment.</param> | |
| public ActionSegment(T[] actionArray) | |
| : this(actionArray ?? System.Array.Empty<T>(), 0, actionArray?.Length ?? 0) { } | |
| /// <summary> | |
| /// Construct an <see cref="ActionSegment{T}"/> with an underlying array | |
| /// and offset, and a length. | |
| /// </summary> | |
| /// <param name="actionArray">The underlying array which this segment has a view into</param> | |
| /// <param name="offset">The zero-based offset into the underlying array.</param> | |
| /// <param name="length">The length of the segment.</param> | |
| public ActionSegment(T[] actionArray, int offset, int length) | |
| { | |
| CheckParameters(actionArray ?? System.Array.Empty<T>(), offset, length); | |
| Array = actionArray ?? System.Array.Empty<T>(); | |
| Offset = offset; | |
| Length = length; | |
| } | |
| /// <summary> | |
| /// Get the underlying <see cref="Array"/> of this segment. | |
| /// </summary> | |
| public T[] Array { get; } | |
| /// <summary> | |
| /// Allows access to the underlying array using array syntax. | |
| /// </summary> | |
| /// <param name="index">The zero-based index of the segment.</param> | |
| /// <exception cref="IndexOutOfRangeException">Thrown when the index is less than 0 or | |
| /// greater than or equal to <see cref="Length"/></exception> | |
| 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; | |
| } | |
| } | |
| /// <summary> | |
| /// Sets the segment of the backing array to all zeros. | |
| /// </summary> | |
| public void Clear() | |
| { | |
| System.Array.Clear(Array, Offset, Length); | |
| } | |
| /// <summary> | |
| /// Check if the segment is empty. | |
| /// </summary> | |
| /// <returns>Whether or not the segment is empty.</returns> | |
| public bool IsEmpty() | |
| { | |
| return Array == null || Array.Length == 0; | |
| } | |
| /// <summary> | |
| /// Returns an enumerator that iterates through the ActionSegment. | |
| /// </summary> | |
| /// <returns>An IEnumerator object that can be used to iterate through the ActionSegment.</returns> | |
| IEnumerator<T> IEnumerable<T>.GetEnumerator() | |
| { | |
| return new Enumerator(this); | |
| } | |
| /// <summary> | |
| /// Returns an enumerator that iterates through the ActionSegment. | |
| /// </summary> | |
| /// <returns>An IEnumerator object that can be used to iterate through the ActionSegment.</returns> | |
| public IEnumerator GetEnumerator() | |
| { | |
| return new Enumerator(this); | |
| } | |
| /// <summary> | |
| /// Indicates whether the current ActionSegment is equal to another ActionSegment. | |
| /// </summary> | |
| /// <param name="obj">An ActionSegment to compare with this ActionSegment.</param> | |
| /// <returns>true if the current ActionSegment is equal to the other parameter; otherwise, false.</returns> | |
| public override bool Equals(object obj) | |
| { | |
| if (!(obj is ActionSegment<T>)) | |
| { | |
| return false; | |
| } | |
| return Equals((ActionSegment<T>)obj); | |
| } | |
| /// <summary> | |
| /// Indicates whether the current ActionSegment is equal to another ActionSegment. | |
| /// </summary> | |
| /// <param name="other">An ActionSegment to compare with this ActionSegment.</param> | |
| /// <returns>true if the current ActionSegment is equal to the other parameter; otherwise, false.</returns> | |
| public bool Equals(ActionSegment<T> other) | |
| { | |
| return Offset == other.Offset && Length == other.Length && Array.SequenceEqual(other.Array); | |
| } | |
| /// <summary> | |
| /// Computes the hash code of the ActionSegment. | |
| /// </summary> | |
| /// <returns>A hash code for the current ActionSegment.</returns> | |
| public override int GetHashCode() | |
| { | |
| unchecked | |
| { | |
| var hashCode = Offset; | |
| hashCode = (hashCode * 397) ^ Length; | |
| hashCode = (hashCode * 397) ^ (Array != null ? Array.GetHashCode() : 0); | |
| return hashCode; | |
| } | |
| } | |
| /// <summary> | |
| /// A private <see cref="IEnumerator{T}"/> for the <see cref="ActionSegment{T}"/> value type which follows its | |
| /// rules of being a view into an underlying <see cref="Array"/>. | |
| /// </summary> | |
| struct Enumerator : IEnumerator<T> | |
| { | |
| 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<T> 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() | |
| { | |
| } | |
| } | |
| } | |
| } | |