/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
* All rights reserved.
*
* Licensed under the Oculus SDK License Agreement (the "License");
* you may not use the Oculus SDK except in compliance with the License,
* which is provided at the time of installation or download, or which
* otherwise accompanies this software in either electronic or hard copy form.
*
* You may obtain a copy of the License at
*
* https://developer.oculus.com/licenses/oculussdk/
*
* Unless required by applicable law or agreed to in writing, the Oculus SDK
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Unity.Collections;
///
/// Allows you to enumerate an IEnumerable in a non allocating way, if possible.
///
/// The type of item contained by the collection.
///
internal readonly struct OVREnumerable : IEnumerable
{
readonly IEnumerable _enumerable;
public OVREnumerable(IEnumerable enumerable) => _enumerable = enumerable;
public Enumerator GetEnumerator() => new Enumerator(_enumerable);
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
public struct Enumerator : IEnumerator
{
enum CollectionType
{
None,
List,
Set,
Queue,
Enumerable,
}
int _listIndex;
readonly CollectionType _type;
readonly int _listCount;
readonly IEnumerator _enumerator;
readonly IReadOnlyList _list;
HashSet.Enumerator _setEnumerator;
Queue.Enumerator _queueEnumerator;
public Enumerator(IEnumerable enumerable)
{
_setEnumerator = default;
_queueEnumerator = default;
_enumerator = null;
_list = null;
_listIndex = -1;
_listCount = 0;
switch (enumerable)
{
case IReadOnlyList list:
_list = list;
_listCount = list.Count;
_type = CollectionType.List;
break;
case HashSet set:
_setEnumerator = set.GetEnumerator();
_type = CollectionType.Set;
break;
case Queue queue:
_queueEnumerator = queue.GetEnumerator();
_type = CollectionType.Queue;
break;
default:
_enumerator = enumerable.GetEnumerator();
_type = CollectionType.Enumerable;
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool MoveNext() => _type switch
{
CollectionType.List => MoveNextList(),
CollectionType.Set => _setEnumerator.MoveNext(),
CollectionType.Queue => _queueEnumerator.MoveNext(),
CollectionType.Enumerable => _enumerator.MoveNext(),
_ => throw new InvalidOperationException($"Unsupported collection type {_type}.")
};
bool MoveNextList()
{
ValidateAndThrow();
return ++_listIndex < _listCount;
}
public void Reset()
{
switch (_type)
{
case CollectionType.List:
ValidateAndThrow();
_listIndex = -1;
break;
case CollectionType.Set:
case CollectionType.Queue:
break;
case CollectionType.Enumerable:
_enumerator.Reset();
break;
}
}
public T Current
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _type switch
{
CollectionType.List => _list[_listIndex],
CollectionType.Set => _setEnumerator.Current,
CollectionType.Queue => _queueEnumerator.Current,
CollectionType.Enumerable => _enumerator.Current,
_ => throw new InvalidOperationException($"Unsupported collection type {_type}.")
};
}
object IEnumerator.Current => Current;
public void Dispose()
{
switch (_type)
{
case CollectionType.List:
break;
case CollectionType.Set:
_setEnumerator.Dispose();
break;
case CollectionType.Queue:
_queueEnumerator.Dispose();
break;
case CollectionType.Enumerable:
_enumerator.Dispose();
break;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
void ValidateAndThrow()
{
if (_listCount != _list.Count)
throw new InvalidOperationException($"The list changed length during enumeration.");
}
}
}
static partial class OVRExtensions
{
///
/// Allows the caller to enumerate an IEnumerable in a non-allocating way, if possible.
///
///
///
/// If you have an IEnumerable, this will allocate IEnumerator:
/// collection) {
/// // Allocates an IEnumerator
/// foreach (var item in collection) {
/// // do something with item
/// }
/// }
/// ]]>
/// However, often the IEnumerable is at least an IReadOnlyList, e.g., a List or Array, its elements can be accessed
/// using the index operator. This custom enumerable will do that:
/// collection) {
/// // Returns a non-allocating struct-based enumerator
/// foreach (var item in collection.ToNonAlloc()) {
/// // do something with item
/// }
/// }
/// ]]>
///
///
/// Note that some safeties cannot be guaranteed, such as mutations to a List during enumeration.
///
/// The collection you wish to enumerate.
/// The type of item in the collection.
/// Returns a non-allocating enumerable.
internal static OVREnumerable ToNonAlloc(this IEnumerable enumerable) => new OVREnumerable(enumerable);
///
/// Copies a collection to a `NativeArray`.
///
///
/// This will copy to a NativeArray in the most efficient way possible. Behavior of
/// in order of decreasing efficiency:
/// - Fixed-size array: single native allocation + memcpy - no managed allocations
/// - IReadOnlyList: single native allocation + iteration - no managed allocations
/// - HashSet: single native allocation + iteration - no managed allocations
/// - Queue: single native allocation + iteration - no managed allocations
/// - IReadOnlyCollection: single native allocation - single managed IEnumerator allocation
/// - ICollection: single native allocation - single managed IEnumerator allocation
/// - Anything else: multiple native allocations (using a growth strategy) - single managed IEnumerator allocation
///
/// The collection to copy to a NativeArray
/// The allocator to use for the returned NativeArray
/// The type of the elements in the collection.
/// Returns a new NativeArray allocated with filled with the elements of
/// .
/// Thrown if is `null`.
internal static NativeArray ToNativeArray(this IEnumerable enumerable, Allocator allocator)
where T : struct
{
if (enumerable == null)
throw new ArgumentNullException(nameof(enumerable));
switch (enumerable)
{
// Easiest case, since NativeArray supports this
case T[] fixedArray: return new NativeArray(fixedArray, allocator);
// Good, since we can iterate the list without allocating
case IReadOnlyList list:
{
var array = new NativeArray(list.Count, allocator, NativeArrayOptions.UninitializedMemory);
for (var i = 0; i < array.Length; i++)
{
array[i] = list[i];
}
return array;
}
// HashSet can be iterated without allocation but doesn't conform to any interface that supports it, so
// it's a special case.
case HashSet set:
{
var array = new NativeArray(set.Count, allocator, NativeArrayOptions.UninitializedMemory);
var index = 0;
foreach (var item in set)
{
array[index++] = item;
}
return array;
}
// Same as HashSet
case Queue queue:
{
var array = new NativeArray(queue.Count, allocator, NativeArrayOptions.UninitializedMemory);
var index = 0;
foreach (var item in queue)
{
array[index++] = item;
}
return array;
}
// Less good because we need to allocate to iterate, but we can know the size beforehand
case IReadOnlyCollection collection:
{
var array = new NativeArray(collection.Count, allocator, NativeArrayOptions.UninitializedMemory);
var index = 0;
foreach (var item in collection)
{
array[index++] = item;
}
return array;
}
// Same as above
case ICollection collection:
{
var array = new NativeArray(collection.Count, allocator, NativeArrayOptions.UninitializedMemory);
var index = 0;
foreach (var item in collection)
{
array[index++] = item;
}
return array;
}
// Fallback to worst case, but only enumerate the collection once
default:
{
var count = 0;
var capacity = 4;
var array = new NativeArray(capacity, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
foreach (var item in enumerable)
{
if (count == capacity)
{
// Grow the array
capacity *= 2;
NativeArray newArray;
using (array)
{
newArray = new NativeArray(capacity, Allocator.Temp,
NativeArrayOptions.UninitializedMemory);
NativeArray.Copy(array, newArray, array.Length);
}
array = newArray;
}
array[count++] = item;
}
using (array)
{
var result = new NativeArray(count, allocator, NativeArrayOptions.UninitializedMemory);
NativeArray.Copy(array, result, count);
return result;
}
}
}
}
}