|
| 1 | +using System; |
| 2 | +using System.Collections; |
| 3 | +using System.Collections.Generic; |
| 4 | +using System.Diagnostics; |
| 5 | + |
| 6 | +namespace Unity.MLAgents.Actuators |
| 7 | +{ |
| 8 | + /// <summary> |
| 9 | + /// ActionSegment{T} is a data structure that allows access to a segment of an underlying array |
| 10 | + /// in order to avoid the copying and allocation of sub-arrays. The segment is defined by |
| 11 | + /// the offset into the original array, and an length. |
| 12 | + /// </summary> |
| 13 | + /// <typeparam name="T">The type of object stored in the underlying <see cref="Array"/></typeparam> |
| 14 | + internal readonly struct ActionSegment<T> : IEnumerable<T>, IEquatable<ActionSegment<T>> |
| 15 | + where T : struct |
| 16 | + { |
| 17 | + /// <summary> |
| 18 | + /// The zero-based offset into the original array at which this segment starts. |
| 19 | + /// </summary> |
| 20 | + public readonly int Offset; |
| 21 | + |
| 22 | + /// <summary> |
| 23 | + /// The number of items this segment can access in the underlying array. |
| 24 | + /// </summary> |
| 25 | + public readonly int Length; |
| 26 | + |
| 27 | + /// <summary> |
| 28 | + /// An Empty segment which has an offset of 0, a Length of 0, and it's underlying array |
| 29 | + /// is also empty. |
| 30 | + /// </summary> |
| 31 | + public static ActionSegment<T> Empty = new ActionSegment<T>(System.Array.Empty<T>(), 0, 0); |
| 32 | + |
| 33 | + static void CheckParameters(T[] actionArray, int offset, int length) |
| 34 | + { |
| 35 | +#if DEBUG |
| 36 | + if (offset + length > actionArray.Length) |
| 37 | + { |
| 38 | + throw new ArgumentOutOfRangeException(nameof(offset), |
| 39 | + $"Arguments offset: {offset} and length: {length} " + |
| 40 | + $"are out of bounds of actionArray: {actionArray.Length}."); |
| 41 | + } |
| 42 | +#endif |
| 43 | + } |
| 44 | + |
| 45 | + /// <summary> |
| 46 | + /// Construct an <see cref="ActionSegment{T}"/> with an underlying array |
| 47 | + /// and offset, and a length. |
| 48 | + /// </summary> |
| 49 | + /// <param name="actionArray">The underlying array which this segment has a view into</param> |
| 50 | + /// <param name="offset">The zero-based offset into the underlying array.</param> |
| 51 | + /// <param name="length">The length of the segment.</param> |
| 52 | + public ActionSegment(T[] actionArray, int offset, int length) |
| 53 | + { |
| 54 | + CheckParameters(actionArray, offset, length); |
| 55 | + Array = actionArray; |
| 56 | + Offset = offset; |
| 57 | + Length = length; |
| 58 | + } |
| 59 | + |
| 60 | + /// <summary> |
| 61 | + /// Get the underlying <see cref="Array"/> of this segment. |
| 62 | + /// </summary> |
| 63 | + public T[] Array { get; } |
| 64 | + |
| 65 | + /// <summary> |
| 66 | + /// Allows access to the underlying array using array syntax. |
| 67 | + /// </summary> |
| 68 | + /// <param name="index">The zero-based index of the segment.</param> |
| 69 | + /// <exception cref="IndexOutOfRangeException">Thrown when the index is less than 0 or |
| 70 | + /// greater than or equal to <see cref="Length"/></exception> |
| 71 | + public T this[int index] |
| 72 | + { |
| 73 | + get |
| 74 | + { |
| 75 | + if (index < 0 || index > Length) |
| 76 | + { |
| 77 | + throw new IndexOutOfRangeException($"Index out of bounds, expected a number between 0 and {Length}"); |
| 78 | + } |
| 79 | + return Array[Offset + index]; |
| 80 | + } |
| 81 | + } |
| 82 | + |
| 83 | + /// <inheritdoc cref="IEnumerable{T}.GetEnumerator"/> |
| 84 | + IEnumerator<T> IEnumerable<T>.GetEnumerator() |
| 85 | + { |
| 86 | + return new Enumerator(this); |
| 87 | + } |
| 88 | + |
| 89 | + /// <inheritdoc cref="IEnumerable{T}"/> |
| 90 | + public IEnumerator GetEnumerator() |
| 91 | + { |
| 92 | + return new Enumerator(this); |
| 93 | + } |
| 94 | + |
| 95 | + /// <inheritdoc cref="ValueType.Equals(object)"/> |
| 96 | + public override bool Equals(object obj) |
| 97 | + { |
| 98 | + if (!(obj is ActionSegment<T>)) |
| 99 | + { |
| 100 | + return false; |
| 101 | + } |
| 102 | + return Equals((ActionSegment<T>)obj); |
| 103 | + } |
| 104 | + |
| 105 | + /// <inheritdoc cref="IEquatable{T}.Equals(T)"/> |
| 106 | + public bool Equals(ActionSegment<T> other) |
| 107 | + { |
| 108 | + return Offset == other.Offset && Length == other.Length && Equals(Array, other.Array); |
| 109 | + } |
| 110 | + |
| 111 | + /// <inheritdoc cref="ValueType.GetHashCode"/> |
| 112 | + public override int GetHashCode() |
| 113 | + { |
| 114 | + unchecked |
| 115 | + { |
| 116 | + var hashCode = Offset; |
| 117 | + hashCode = (hashCode * 397) ^ Length; |
| 118 | + hashCode = (hashCode * 397) ^ (Array != null ? Array.GetHashCode() : 0); |
| 119 | + return hashCode; |
| 120 | + } |
| 121 | + } |
| 122 | + |
| 123 | + /// <summary> |
| 124 | + /// A private <see cref="IEnumerator{T}"/> for the <see cref="ActionSegment{T}"/> value type which follows its |
| 125 | + /// rules of being a view into an underlying <see cref="Array"/>. |
| 126 | + /// </summary> |
| 127 | + struct Enumerator : IEnumerator<T> |
| 128 | + { |
| 129 | + readonly T[] m_Array; |
| 130 | + readonly int m_Start; |
| 131 | + readonly int m_End; // cache Offset + Count, since it's a little slow |
| 132 | + int m_Current; |
| 133 | + |
| 134 | + internal Enumerator(ActionSegment<T> arraySegment) |
| 135 | + { |
| 136 | + Debug.Assert(arraySegment.Array != null); |
| 137 | + Debug.Assert(arraySegment.Offset >= 0); |
| 138 | + Debug.Assert(arraySegment.Length >= 0); |
| 139 | + Debug.Assert(arraySegment.Offset + arraySegment.Length <= arraySegment.Array.Length); |
| 140 | + |
| 141 | + m_Array = arraySegment.Array; |
| 142 | + m_Start = arraySegment.Offset; |
| 143 | + m_End = arraySegment.Offset + arraySegment.Length; |
| 144 | + m_Current = arraySegment.Offset - 1; |
| 145 | + } |
| 146 | + |
| 147 | + public bool MoveNext() |
| 148 | + { |
| 149 | + if (m_Current < m_End) |
| 150 | + { |
| 151 | + m_Current++; |
| 152 | + return m_Current < m_End; |
| 153 | + } |
| 154 | + return false; |
| 155 | + } |
| 156 | + |
| 157 | + public T Current |
| 158 | + { |
| 159 | + get |
| 160 | + { |
| 161 | + if (m_Current < m_Start) |
| 162 | + throw new InvalidOperationException("Enumerator not started."); |
| 163 | + if (m_Current >= m_End) |
| 164 | + throw new InvalidOperationException("Enumerator has reached the end already."); |
| 165 | + return m_Array[m_Current]; |
| 166 | + } |
| 167 | + } |
| 168 | + |
| 169 | + object IEnumerator.Current => Current; |
| 170 | + |
| 171 | + void IEnumerator.Reset() |
| 172 | + { |
| 173 | + m_Current = m_Start - 1; |
| 174 | + } |
| 175 | + |
| 176 | + public void Dispose() |
| 177 | + { |
| 178 | + } |
| 179 | + } |
| 180 | + } |
| 181 | +} |
0 commit comments