/* * 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; } } } } }