| File: Utilities\Deque.cs | Web Access |
| Project: src\roslyn\src\Workspaces\Core\Portable\Microsoft.CodeAnalysis.Workspaces.csproj (Microsoft.CodeAnalysis.Workspaces) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. using System; using System.Buffers; using System.Diagnostics; #if NET using System.Runtime.CompilerServices; #endif using Microsoft.CodeAnalysis.PooledObjects; namespace Microsoft.CodeAnalysis.Utilities; /// <summary> /// A simple double-ended queue backed by a circular buffer. Supports O(1) push/pop at both ends /// and O(1) indexed access. Pooled via <see cref="GetInstance"/> to avoid allocation churn in /// hot loops. /// </summary> internal sealed class Deque<T> : IPooled { private static readonly ObjectPool<Deque<T>> s_pool = new(static () => new Deque<T>()); private T[] _array; private int _head; private int _count; private Deque() { _array = []; } public int Count => _count; public T this[int index] { get { Debug.Assert((uint)index < (uint)_count); return _array[(_head + index) % _array.Length]; } } public T First { get { Debug.Assert(_count > 0); return _array[_head]; } } public T Last { get { Debug.Assert(_count > 0); return _array[(_head + _count - 1) % _array.Length]; } } public void AddLast(T item) { EnsureCapacity(_count + 1); _array[(_head + _count) % _array.Length] = item; _count++; } public T RemoveFirst() { Debug.Assert(_count > 0); var item = _array[_head]; if (MustClearReferences()) _array[_head] = default!; _head = (_head + 1) % _array.Length; _count--; return item; } public T RemoveLast() { Debug.Assert(_count > 0); _count--; var index = (_head + _count) % _array.Length; var item = _array[index]; if (MustClearReferences()) _array[index] = default!; return item; } private void EnsureCapacity(int min) { if (_array.Length >= min) return; var newCapacity = Math.Max(4, _array.Length * 2); while (newCapacity < min) newCapacity *= 2; var newArray = ArrayPool<T>.Shared.Rent(newCapacity); if (_count > 0) { if (_head + _count <= _array.Length) { Array.Copy(_array, _head, newArray, 0, _count); } else { var firstPart = _array.Length - _head; Array.Copy(_array, _head, newArray, 0, firstPart); Array.Copy(_array, 0, newArray, firstPart, _count - firstPart); } } ReturnArray(); _array = newArray; _head = 0; } private void ReturnArray() { if (_array.Length > 0) ArrayPool<T>.Shared.Return(_array, clearArray: MustClearReferences()); } private static bool MustClearReferences() { #if NET return RuntimeHelpers.IsReferenceOrContainsReferences<T>(); #else return true; #endif } void IPooled.Free(bool discardLargeInstances) { ReturnArray(); _array = []; _head = 0; _count = 0; s_pool.Free(this); } public static PooledDisposer<Deque<T>> GetInstance(out Deque<T> instance) { instance = s_pool.Allocate(); Debug.Assert(instance._count == 0); return new PooledDisposer<Deque<T>>(instance); } }