/******************************************************************************
* Copyright (C) Ultraleap, Inc. 2011-2021. *
* *
* Use subject to the terms of the Apache License 2.0 available at *
* http://www.apache.org/licenses/LICENSE-2.0, or another agreement *
* between Ultraleap and you, your company or other organization. *
******************************************************************************/
using System;
using System.Collections;
using System.Collections.Generic;
namespace Leap.Unity.Query
{
public static class QueryConversionExtensions
{
///
/// Constructs a new Query from the given Collection.
///
public static Query Query(this ICollection collection)
{
return new Query(collection);
}
///
/// Constructs a new Query from the given IEnumerable
///
public static Query Query(this IEnumerable enumerable)
{
List list = Pool>.Spawn();
try
{
list.AddRange(enumerable);
return new Query(list);
}
finally
{
list.Clear();
Pool>.Recycle(list);
}
}
///
/// Constructs a new Query from the given IEnumerator
///
public static Query Query(this IEnumerator enumerator)
{
List list = Pool>.Spawn();
try
{
while (enumerator.MoveNext())
{
list.Add(enumerator.Current);
}
return new Query(list);
}
finally
{
list.Clear();
Pool>.Recycle(list);
}
}
///
/// Constructs a new Query from the given two dimensional array.
///
public static Query Query(this T[,] array)
{
var dst = ArrayPool.Spawn(array.GetLength(0) * array.GetLength(1));
int dstIndex = 0;
for (int i = 0; i < array.GetLength(0); i++)
{
for (int j = 0; j < array.GetLength(1); j++)
{
dst[dstIndex++] = array[i, j];
}
}
return new Query(dst, array.GetLength(0) * array.GetLength(1));
}
}
///
/// A Query object is a type of immutable ordered collection of elements that can be
/// used to perform useful queries. These queries are very similar to LINQ style
/// queries, providing useful methods such as Where, Select, Concat, etc....
///
/// The Query struct and its interfaces use a pooling strategy backed by ArrayPool
/// to incur an amortized cost of zero GC allocations.
///
/// A Query struct is immutable, and so cannot be modified once it has been created.
/// You can use a query in few ways:
/// - The simplest way is to call an operator method such as Where or Select. These
/// methods CONSUME the query to produce a new query. Trying to use the original
/// query after it has been consumed will cause a runtime error.
/// - The next way is to call a collapsing operator, which will consume the query
/// and produce a non-query value or other side-effect. Examples of collapsing
/// operators are First, Last, or ElementAt.
/// - The last way to use a query is to Deconstruct it, by calling a Deconstruct
/// method to destroy the query and get access to its underlying data. You
/// will be responsible for cleaning up or disposing the data you get.
///
public struct Query
{
private T[] _array;
private int _count;
private Validator _validator;
///
/// Constructs a new query given a source array and a count. The query assumes
/// ownership of the array, so you should not use it or store it
/// after the query is constructed.
///
public Query(T[] array, int count)
{
if (array == null)
{
throw new ArgumentNullException("array");
}
if (count < 0)
{
throw new ArgumentException("Count must be non-negative, but was " + count);
}
if (count > array.Length)
{
throw new ArgumentException("Count was " + count + " but the provided array only had a length of " + array.Length);
}
_array = array;
_count = count;
_validator = Validator.Spawn();
}
///
/// Constructs a new query of the given collection.
///
public Query(ICollection collection)
{
_array = ArrayPool.Spawn(collection.Count);
_count = collection.Count;
collection.CopyTo(_array, 0);
_validator = Validator.Spawn();
}
///
/// Constructs a query that is an exact copy of another query.
///
public Query(Query other)
{
other._validator.Validate();
_array = ArrayPool.Spawn(other._count);
_count = other._count;
Array.Copy(other._array, _array, _count);
_validator = Validator.Spawn();
}
//Certain operators cannot be implemented as extension methods due to the way
//the generic arguments are to be consumed by the user, so there are implemented
//directly here in the Query class.
#region DIRECT IMPLEMENTED OPERATORS
///
/// Returns a new Query representing only the items of the current Query that
/// are of a specific type.
///
/// For example
/// ("A", 1, null, 5.0f, 900, "hello").Query().OfType()
/// would result in
/// ("A", "hello")
///
public Query OfType() where K : T
{
_validator.Validate();
var dstArray = ArrayPool.Spawn(_count);
int dstCount = 0;
for (int i = 0; i < _count; i++)
{
if (_array[i] is K)
{
dstArray[dstCount++] = (K)_array[i];
}
}
Dispose();
return new Query(dstArray, dstCount);
}
///
/// Returns a new Query representing the current query sequence where each element is cast
/// to a new type.
///
public Query Cast() where K : class
{
return this.Select(item => item as K);
}
#endregion
///
/// Disposes of the resources that this query holds. The Query cannot be
/// used after this method is called.
///
public void Dispose()
{
_validator.Validate();
ArrayPool.Recycle(_array);
Validator.Invalidate(_validator);
_array = null;
_count = 0;
}
///
/// Deconstructs this Query into its base elements, the array and the count.
/// The caller assumes ownership of the array and is responsible for managing
/// its lifecycle. The Query cannot be used after this method is called.
///
public void Deconstruct(out T[] array, out int count)
{
_validator.Validate();
array = _array;
count = _count;
Validator.Invalidate(_validator);
_array = null;
_count = 0;
}
///
/// Deconstructs this Query into a simple QuerySlice construct. This is
/// simply a utility overload of the regular Deconstruct method. The
/// user is still responsible for managing the memory lifecycle of the returned
/// slice. The Query cannot be used after this method is called.
///
public QuerySlice Deconstruct()
{
T[] array;
int count;
Deconstruct(out array, out count);
return new QuerySlice(array, count);
}
public Enumerator GetEnumerator()
{
_validator.Validate();
T[] array;
int count;
Deconstruct(out array, out count);
return new Enumerator(array, count);
}
public struct Enumerator : IEnumerator
{
private T[] _array;
private int _count;
private int _nextIndex;
public T Current { get; private set; }
public Enumerator(T[] array, int count)
{
_array = array;
_count = count;
_nextIndex = 0;
Current = default(T);
}
object IEnumerator.Current
{
get
{
if (_nextIndex == 0)
{
throw new InvalidOperationException();
}
return Current;
}
}
public bool MoveNext()
{
if (_nextIndex >= _count)
{
return false;
}
Current = _array[_nextIndex++];
return true;
}
public void Dispose()
{
ArrayPool.Recycle(_array);
}
public void Reset() { throw new InvalidOperationException(); }
}
public struct QuerySlice : IDisposable
{
public readonly T[] BackingArray;
public readonly int Count;
public QuerySlice(T[] array, int count)
{
BackingArray = array;
Count = count;
}
public T this[int index]
{
get
{
return BackingArray[index];
}
}
public void Dispose()
{
ArrayPool.Recycle(BackingArray);
}
}
private struct Validator
{
private static int _nextId = 1;
private Id _idRef;
private int _idValue;
public void Validate()
{
if (_idValue == 0)
{
throw new InvalidOperationException("This Query is not valid, you cannot construct a Query using the default constructor.");
}
if (_idRef == null || _idRef.value != _idValue)
{
throw new InvalidOperationException("This Query has already been disposed. A Query can only be used once before it is automatically disposed.");
}
}
public static Validator Spawn()
{
Id id = Pool.Spawn();
id.value = _nextId++;
return new Validator()
{
_idRef = id,
_idValue = id.value
};
}
public static void Invalidate(Validator validator)
{
validator._idRef.value = -1;
Pool.Recycle(validator._idRef);
}
private class Id
{
public int value;
}
}
}
}