using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
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, 0, actionArray.Length) { }
///
/// 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, offset, length);
#endif
Array = actionArray;
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);
}
///
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 && Equals(Array, 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()
{
}
}
}
}