• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CalionVarduk / LfrlAnvil / 12652537778

07 Jan 2025 01:22PM UTC coverage: 96.992% (+0.08%) from 96.91%
12652537778

push

github

CalionVarduk
Global:
- Add logo images;
- Include package icons;
- Include docfx icon change;
- Update LICENSE years;

15409 of 16250 branches covered (94.82%)

Branch coverage included in aggregate %.

43660 of 44651 relevant lines covered (97.78%)

32107.65 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.5
/src/LfrlAnvil.Core/Memory/MemorySequencePool.cs
1
// Copyright 2024 Łukasz Furlepa
2
// 
3
// Licensed under the Apache License, Version 2.0 (the "License");
4
// you may not use this file except in compliance with the License.
5
// You may obtain a copy of the License at
6
// 
7
//     http://www.apache.org/licenses/LICENSE-2.0
8
// 
9
// Unless required by applicable law or agreed to in writing, software
10
// distributed under the License is distributed on an "AS IS" BASIS,
11
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
// See the License for the specific language governing permissions and
13
// limitations under the License.
14

15
using System;
16
using System.Collections.Generic;
17
using System.Diagnostics;
18
using System.Diagnostics.Contracts;
19
using System.Numerics;
20
using System.Runtime.CompilerServices;
21
using System.Runtime.InteropServices;
22
using LfrlAnvil.Internal;
23

24
namespace LfrlAnvil.Memory;
25

26
/// <summary>
27
/// Represents a pool of <see cref="RentedMemorySequence{T}"/> instances.
28
/// </summary>
29
/// <typeparam name="T">Element type.</typeparam>
30
public class MemorySequencePool<T>
31
{
32
    /// <summary>
33
    /// A lightweight <see cref="MemorySequencePool{T}"/> state report container.
34
    /// </summary>
35
    public readonly struct ReportInfo
36
    {
37
        private readonly MemorySequencePool<T>? _pool;
38

39
        internal ReportInfo(MemorySequencePool<T> pool)
40
        {
41
            _pool = pool;
51✔
42
        }
51✔
43

44
        /// <summary>
45
        /// Total number of allocated pool segments.
46
        /// </summary>
47
        public int AllocatedSegments => _pool?._segments.Count ?? 0;
3✔
48

49
        /// <summary>
50
        /// Number of active pool segments.
51
        /// </summary>
52
        public int ActiveSegments => (_pool?._tailNode?.LastIndex.Segment ?? -1) + 1;
3✔
53

54
        /// <summary>
55
        /// Number of cached underlying pool nodes.
56
        /// </summary>
57
        public int CachedNodes => _pool?._nodeCache.Count ?? 0;
12✔
58

59
        /// <summary>
60
        /// Number of active underlying pool nodes.
61
        /// </summary>
62
        public int ActiveNodes
63
        {
64
            get
65
            {
66
                var count = 0;
3✔
67
                var node = _pool?._tailNode;
3✔
68
                while ( node is not null )
11✔
69
                {
70
                    ++count;
8✔
71
                    node = node.Prev;
8✔
72
                }
73

74
                return count;
3✔
75
            }
76
        }
77

78
        /// <summary>
79
        /// Number of fragmented underlying pool nodes.
80
        /// </summary>
81
        public int FragmentedNodes => _pool?._fragmentationHeap.Count ?? 0;
6✔
82

83
        /// <summary>
84
        /// Number of active elements.
85
        /// </summary>
86
        public int ActiveElements
87
        {
88
            get
89
            {
90
                var node = _pool?._tailNode;
3✔
91
                return node is not null ? (node.LastIndex.Segment << _pool!._segmentLengthLog2) + node.LastIndex.Element + 1 : 0;
3✔
92
            }
93
        }
94

95
        /// <summary>
96
        /// Number of fragmented elements.
97
        /// </summary>
98
        public int FragmentedElements
99
        {
100
            get
101
            {
102
                var count = 0;
12✔
103
                var fragmentationHeapLength = _pool?._fragmentationHeap.Count ?? 0;
12✔
104
                if ( fragmentationHeapLength == 0 )
12✔
105
                    return count;
6✔
106

107
                for ( var i = 0; i < fragmentationHeapLength; ++i )
28✔
108
                    count += _pool!._fragmentationHeap[i].Length;
8✔
109

110
                return count;
6✔
111
            }
112
        }
113

114
        /// <summary>
115
        /// Creates a new <see cref="IEnumerable{T}"/> instance that contains sizes of all fragmented underlying pool nodes.
116
        /// </summary>
117
        /// <returns>New <see cref="IEnumerable{T}"/> instance.</returns>
118
        [Pure]
119
        public IEnumerable<int> GetFragmentedNodeSizes()
120
        {
121
            var fragmentationHeapLength = _pool?._fragmentationHeap.Count ?? 0;
18✔
122
            for ( var i = 0; i < fragmentationHeapLength; ++i )
76✔
123
                yield return _pool!._fragmentationHeap[i].Length;
20✔
124
        }
18✔
125

126
        /// <summary>
127
        /// Creates a new <see cref="IEnumerable{T}"/> instance that contains all currently rented memory sequences.
128
        /// </summary>
129
        /// <returns>New <see cref="IEnumerable{T}"/> instance.</returns>
130
        [Pure]
131
        public IEnumerable<RentedMemorySequence<T>> GetRentedNodes()
132
        {
133
            var node = _pool?._tailNode;
44✔
134
            while ( node is not null )
120✔
135
            {
136
                if ( node.IsReusable )
90✔
137
                {
138
                    node = node.Prev;
30✔
139
                    if ( node is null )
30✔
140
                        break;
141

142
                    Assume.False( node.IsReusable );
143
                }
144

145
                yield return new RentedMemorySequence<T>( node );
76✔
146

147
                node = node.Prev;
76✔
148
            }
149
        }
44✔
150
    }
151

152
    private readonly int _segmentLengthLog2;
153
    private Node? _tailNode;
154
    private StackSlim<Node> _nodeCache;
155
    private ListSlim<Node> _fragmentationHeap;
156
    private ListSlim<T[]> _segments;
157

158
    /// <summary>
159
    /// Creates a new <see cref="MemorySequencePool{T}"/> instance.
160
    /// </summary>
161
    /// <param name="minSegmentLength">Minimum single pool segment length. The actual value will be rounded up to a power of two.</param>
162
    /// <exception cref="ArgumentOutOfRangeException">
163
    /// When <paramref name="minSegmentLength"/> is less than <b>1</b> or greater than <b>2^30</b>.
164
    /// </exception>
165
    public MemorySequencePool(int minSegmentLength)
4,823✔
166
    {
167
        Ensure.IsGreaterThan( minSegmentLength, 0 );
4,823✔
168
        Ensure.IsLessThanOrEqualTo( minSegmentLength, 1 << 30 );
4,821✔
169

170
        _segmentLengthLog2 = BitOperations.Log2( BitOperations.RoundUpToPowerOf2( unchecked( ( uint )minSegmentLength ) ) );
4,819✔
171
        SegmentLength = 1 << _segmentLengthLog2;
4,819✔
172
        ClearReturnedSequences = true;
4,819✔
173

174
        _tailNode = null;
4,819✔
175
        _nodeCache = StackSlim<Node>.Create();
4,819✔
176
        _fragmentationHeap = ListSlim<Node>.Create();
4,819✔
177
        _segments = ListSlim<T[]>.Create();
4,819✔
178
    }
4,819✔
179

180
    /// <summary>
181
    /// Specifies whether or not this pool clears the contents of memory sequences that get returned to the pool.
182
    /// </summary>
183
    public bool ClearReturnedSequences { get; set; }
6,475✔
184

185
    /// <summary>
186
    /// Length of a single pool segment.
187
    /// </summary>
188
    public int SegmentLength { get; }
18,603✔
189

190
    /// <summary>
191
    /// Creates a new <see cref="ReportInfo"/> instance.
192
    /// </summary>
193
    public ReportInfo Report => new ReportInfo( this );
51✔
194

195
    /// <summary>
196
    /// Creates a new <see cref="RentedMemorySequence{T}"/> instance from this pool.
197
    /// </summary>
198
    /// <param name="length">Size of the rented sequence.</param>
199
    /// <returns>
200
    /// New <see cref="RentedMemorySequence{T}"/> instance,
201
    /// or <see cref="RentedMemorySequence{T}.Empty"/> when <paramref name="length"/> is less than <b>1</b>.
202
    /// </returns>
203
    /// <remarks>This method attempts to reuse fragmented segments.</remarks>
204
    public RentedMemorySequence<T> Rent(int length)
205
    {
206
        return length <= 0 ? RentedMemorySequence<T>.Empty : new RentedMemorySequence<T>( RentNode( length ) );
1,131✔
207
    }
208

209
    /// <summary>
210
    /// Creates a new <see cref="RentedMemorySequence{T}"/> instance from this pool.
211
    /// </summary>
212
    /// <param name="length">Initial size of the rented sequence. Equal to <b>0</b> by default.</param>
213
    /// <returns>New <see cref="RentedMemorySequence{T}"/> instance.</returns>
214
    /// <remarks>This method always uses or allocates segments at the tail of the pool.</remarks>
215
    public RentedMemorySequence<T> GreedyRent(int length = 0)
216
    {
217
        length = Math.Max( length, 0 );
1,234✔
218
        return new RentedMemorySequence<T>( AllocateAtTail( length ) );
1,234✔
219
    }
220

221
    /// <summary>
222
    /// Attempts to deallocate unused segments at the tail of this pool.
223
    /// </summary>
224
    public void TrimExcess()
225
    {
226
        var cachedTailSegmentCount = _segments.Count;
2✔
227
        if ( _tailNode is not null )
2✔
228
            cachedTailSegmentCount -= _tailNode.LastIndex.Segment + 1;
1✔
229

230
        _segments.RemoveLastRange( cachedTailSegmentCount );
2✔
231
        _segments.ResetCapacity();
2✔
232
        _fragmentationHeap.ResetCapacity();
2✔
233
        _nodeCache.Clear();
2✔
234
        _nodeCache.ResetCapacity();
2✔
235
    }
2✔
236

237
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
238
    private Node RentNode(int length)
239
    {
240
        Assume.IsGreaterThan( length, 0 );
241

242
        if ( _fragmentationHeap.Count > 0 )
1,126✔
243
        {
244
            var largestNode = _fragmentationHeap.First();
42✔
245
            if ( largestNode.Length >= length )
42✔
246
                return AllocateAtLargestFragmentedNode( largestNode, length );
40✔
247
        }
248

249
        return AllocateAtTail( length );
1,086✔
250
    }
251

252
    [Pure]
253
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
254
    private ArraySequenceIndex OffsetIndex(ArraySequenceIndex index, int offset)
255
    {
256
        return index.Add( offset, _segmentLengthLog2 );
8,114✔
257
    }
258

259
    [Pure]
260
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
261
    private ArraySequenceIndex GetMinusOneIndex()
262
    {
263
        return ArraySequenceIndex.MinusOne( SegmentLength );
3,758✔
264
    }
265

266
    private Node AllocateAtTail(int length)
267
    {
268
        var node = Node.CreateTailNode( this, length );
2,320✔
269
        AllocateSegments( node.LastIndex.Segment );
2,320✔
270
        return node;
2,320✔
271
    }
272

273
    private Node AllocateAtLargestFragmentedNode(Node node, int length)
274
    {
275
        Assume.Equals( node, _fragmentationHeap.First() );
276
        Assume.IsLessThanOrEqualTo( length, node.Length );
277

278
        if ( node.Length == length )
40✔
279
        {
280
            node.DeactivateFragmentationIndex();
23✔
281
            PopFromFragmentationHeap();
23✔
282
            return node;
23✔
283
        }
284

285
        var extractedNode = node.ExtractNode( length );
17✔
286
        return extractedNode;
17✔
287
    }
288

289
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
290
    private void AllocateSegments(int lastSegmentIndex)
291
    {
292
        Assume.IsNotNull( _tailNode );
293

294
        var segmentsToAllocate = lastSegmentIndex - _segments.Count + 1;
2,446✔
295
        for ( var i = 0; i < segmentsToAllocate; ++i )
8,038✔
296
            _segments.Add( new T[SegmentLength] );
1,573✔
297
    }
2,446✔
298

299
    private void AddToFragmentationHeap(Node node)
300
    {
301
        Assume.IsGreaterThan( node.Length, 0 );
302
        Assume.Equals( node.FragmentationIndex, Node.InactiveFragmentationIndex );
303

304
        node.UpdateFragmentationIndex( _fragmentationHeap.Count );
73✔
305
        _fragmentationHeap.Add( node );
73✔
306
        FixUpFragmentationHeap( node );
73✔
307
    }
73✔
308

309
    private void PopFromFragmentationHeap()
310
    {
311
        if ( _fragmentationHeap.Count == 1 )
23✔
312
        {
313
            _fragmentationHeap.RemoveLast();
19✔
314
            return;
19✔
315
        }
316

317
        ref var first = ref _fragmentationHeap.First();
4✔
318
        var last = Unsafe.Add( ref first, _fragmentationHeap.Count - 1 );
4✔
319
        first = last;
4✔
320
        last.UpdateFragmentationIndex( 0 );
4✔
321
        _fragmentationHeap.RemoveLast();
4✔
322
        FixDownFragmentationHeap( last );
4✔
323
    }
4✔
324

325
    private void RemoveFromFragmentationHeap(Node node)
326
    {
327
        ref var first = ref _fragmentationHeap.First();
19✔
328
        var last = Unsafe.Add( ref first, _fragmentationHeap.Count - 1 );
19✔
329

330
        if ( ReferenceEquals( node, last ) )
19✔
331
        {
332
            node.DeactivateFragmentationIndex();
15✔
333
            _fragmentationHeap.RemoveLast();
15✔
334
            return;
15✔
335
        }
336

337
        ref var target = ref Unsafe.Add( ref first, node.FragmentationIndex );
4✔
338
        target = last;
4✔
339

340
        last.UpdateFragmentationIndex( node.FragmentationIndex );
4✔
341
        node.DeactivateFragmentationIndex();
4✔
342
        _fragmentationHeap.RemoveLast();
4✔
343
        FixRelativeFragmentationHeap( last, node.Length );
4✔
344
    }
4✔
345

346
    private void FixUpFragmentationHeap(Node node)
347
    {
348
        Assume.IsGreaterThanOrEqualTo( node.FragmentationIndex, 0 );
349
        var p = (node.FragmentationIndex - 1) >> 1;
100✔
350
        ref var first = ref _fragmentationHeap.First();
100✔
351

352
        while ( node.FragmentationIndex > 0 )
123✔
353
        {
354
            var parent = Unsafe.Add( ref first, p );
31✔
355
            if ( node.Length <= parent.Length )
31✔
356
                break;
357

358
            ref var target = ref Unsafe.Add( ref first, node.FragmentationIndex )!;
23✔
359
            target = parent;
23✔
360

361
            target = ref Unsafe.Add( ref first, parent.FragmentationIndex )!;
23✔
362
            target = node;
23✔
363

364
            node.SwapFragmentationIndex( parent );
23✔
365
            p = (p - 1) >> 1;
23✔
366
        }
367
    }
100✔
368

369
    private void FixDownFragmentationHeap(Node node)
370
    {
371
        Assume.IsGreaterThanOrEqualTo( node.FragmentationIndex, 0 );
372
        var l = (node.FragmentationIndex << 1) + 1;
29✔
373
        ref var first = ref _fragmentationHeap.First();
29✔
374

375
        while ( l < _fragmentationHeap.Count )
37✔
376
        {
377
            var child = Unsafe.Add( ref first, l );
11✔
378
            var nodeToSwap = node.Length < child.Length ? child : node;
11✔
379

380
            var r = l + 1;
11✔
381
            if ( r < _fragmentationHeap.Count )
11✔
382
            {
383
                child = Unsafe.Add( ref first, r );
10✔
384
                if ( nodeToSwap.Length < child.Length )
10✔
385
                    nodeToSwap = child;
3✔
386
            }
387

388
            if ( ReferenceEquals( node, nodeToSwap ) )
11✔
389
                break;
390

391
            ref var target = ref Unsafe.Add( ref first, node.FragmentationIndex )!;
8✔
392
            target = nodeToSwap;
8✔
393

394
            target = ref Unsafe.Add( ref first, nodeToSwap.FragmentationIndex )!;
8✔
395
            target = node;
8✔
396

397
            node.SwapFragmentationIndex( nodeToSwap );
8✔
398
            l = (node.FragmentationIndex << 1) + 1;
8✔
399
        }
400
    }
29✔
401

402
    [MethodImpl( MethodImplOptions.AggressiveInlining )]
403
    private void FixRelativeFragmentationHeap(Node node, int oldLength)
404
    {
405
        if ( node.Length < oldLength )
4✔
406
            FixDownFragmentationHeap( node );
3✔
407
        else
408
            FixUpFragmentationHeap( node );
1✔
409
    }
1✔
410

411
    internal sealed class Node
412
    {
413
        internal const int CachedFragmentationIndex = -2;
414
        internal const int InactiveFragmentationIndex = -1;
415

416
        private Node(MemorySequencePool<T> pool)
2,273✔
417
        {
418
            Pool = pool;
2,273✔
419
            FirstIndex = ArraySequenceIndex.Zero;
2,273✔
420
            LastIndex = pool.GetMinusOneIndex();
2,273✔
421
            Length = 0;
2,273✔
422
            FragmentationIndex = InactiveFragmentationIndex;
2,273✔
423
            Prev = null;
2,273✔
424
            Next = null;
2,273✔
425
        }
2,273✔
426

427
        internal MemorySequencePool<T> Pool { get; }
32,717✔
428
        internal ArraySequenceIndex FirstIndex { get; private set; }
15,067✔
429
        internal ArraySequenceIndex LastIndex { get; private set; }
10,120✔
430
        internal int Length { get; private set; }
12,336✔
431
        internal int FragmentationIndex { get; private set; }
8,257✔
432
        internal Node? Prev { get; private set; }
5,943✔
433
        internal Node? Next { get; private set; }
5,025✔
434
        internal bool IsReusable => FragmentationIndex != InactiveFragmentationIndex;
3,858✔
435

436
        [Pure]
437
        public override string ToString()
438
        {
439
            return $"({FirstIndex}) : ({LastIndex}) [{Length}], {nameof( FragmentationIndex )} = {FragmentationIndex}";
×
440
        }
441

442
        internal static Node CreateTailNode(MemorySequencePool<T> pool, int length)
443
        {
444
            var firstIndex = pool._tailNode is null ? ArraySequenceIndex.Zero : pool.OffsetIndex( pool._tailNode.LastIndex, 1 );
2,320✔
445
            var node = GetOrCreateNode( pool );
2,320✔
446
            node.FirstIndex = firstIndex;
2,320✔
447
            node.Length = length;
2,320✔
448
            node.LastIndex = length == 0 ? node.FirstIndex.Decrement( pool.SegmentLength ) : node.OffsetFirstIndex( length - 1 );
2,320✔
449

450
            if ( pool._tailNode is not null )
2,320✔
451
            {
452
                Assume.IsNull( pool._tailNode.Next );
453
                node.Prev = pool._tailNode;
565✔
454
                node.Prev.Next = node;
565✔
455
            }
456

457
            pool._tailNode = node;
2,320✔
458
            return node;
2,320✔
459
        }
460

461
        internal Node ExtractNode(int length)
462
        {
463
            Assume.IsGreaterThan( length, 0 );
464
            Assume.IsGreaterThanOrEqualTo( FragmentationIndex, 0 );
465

466
            var node = GetOrCreateNode( Pool );
17✔
467
            node.FirstIndex = FirstIndex;
17✔
468
            node.Length = length;
17✔
469
            node.LastIndex = OffsetFirstIndex( length - 1 );
17✔
470

471
            node.Prev = Prev;
17✔
472
            if ( Prev is not null )
17✔
473
                Prev.Next = node;
13✔
474

475
            Prev = node;
17✔
476
            node.Next = this;
17✔
477

478
            DecreaseFragmentedLength( length );
17✔
479
            return node;
17✔
480
        }
481

482
        [Pure]
483
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
484
        internal ArraySequenceIndex OffsetFirstIndex(int offset)
485
        {
486
            return Pool.OffsetIndex( FirstIndex, offset );
7,169✔
487
        }
488

489
        [Pure]
490
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
491
        internal T[] GetAbsoluteSegment(int index)
492
        {
493
            ref var first = ref Pool._segments.First();
6,838✔
494
            return Unsafe.Add( ref first, index );
6,838✔
495
        }
496

497
        [Pure]
498
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
499
        internal T GetElement(int index)
500
        {
501
            Assume.IsGreaterThanOrEqualTo( index, 0 );
502
            Assume.IsLessThan( index, Length );
503

504
            var sequenceIndex = OffsetFirstIndex( index );
69✔
505
            var segment = GetAbsoluteSegment( sequenceIndex.Segment );
69✔
506
            return segment[sequenceIndex.Element];
69✔
507
        }
508

509
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
510
        internal void SetElement(int index, T value)
511
        {
512
            Assume.IsGreaterThanOrEqualTo( index, 0 );
513
            Assume.IsLessThan( index, Length );
514

515
            var sequenceIndex = OffsetFirstIndex( index );
3,601✔
516
            var segment = GetAbsoluteSegment( sequenceIndex.Segment );
3,601✔
517
            segment[sequenceIndex.Element] = value;
3,601✔
518
        }
3,601✔
519

520
        [Pure]
521
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
522
        internal ref T GetElementRef(int index)
523
        {
524
            Assume.IsGreaterThanOrEqualTo( index, 0 );
525
            Assume.IsLessThan( index, Length );
526

527
            var sequenceIndex = OffsetFirstIndex( index );
2✔
528
            var segment = GetAbsoluteSegment( sequenceIndex.Segment );
2✔
529
            return ref segment[sequenceIndex.Element];
2✔
530
        }
531

532
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
533
        internal void ClearSegments()
534
        {
535
            if ( Length > 0 )
1,321✔
536
                ClearSegments( FirstIndex, LastIndex );
228✔
537
        }
1,321✔
538

539
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
540
        internal void ClearSegments(int startIndex, int length)
541
        {
542
            if ( length == 0 )
9✔
543
                return;
1✔
544

545
            var first = OffsetFirstIndex( startIndex );
8✔
546
            var last = Pool.OffsetIndex( first, length - 1 );
8✔
547
            ClearSegments( first, last );
8✔
548
        }
8✔
549

550
        [Pure]
551
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
552
        internal int IndexOf(T item)
553
        {
554
            return Length == 0 ? -1 : IndexOf( item, FirstIndex, LastIndex );
27✔
555
        }
556

557
        [Pure]
558
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
559
        internal int IndexOf(T item, int startIndex, int length)
560
        {
561
            if ( length == 0 )
35✔
562
                return -1;
1✔
563

564
            var first = OffsetFirstIndex( startIndex );
34✔
565
            var last = Pool.OffsetIndex( first, length - 1 );
34✔
566
            return IndexOf( item, first, last );
34✔
567
        }
568

569
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
570
        internal void CopyTo(Span<T> span)
571
        {
572
            if ( Length > 0 )
24✔
573
                CopyTo( span, FirstIndex, LastIndex );
23✔
574
        }
24✔
575

576
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
577
        internal void CopyTo(Span<T> span, int startIndex, int length)
578
        {
579
            if ( length == 0 )
98✔
580
                return;
1✔
581

582
            var first = OffsetFirstIndex( startIndex );
97✔
583
            var last = Pool.OffsetIndex( first, length - 1 );
97✔
584
            CopyTo( span, first, last );
97✔
585
        }
97✔
586

587
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
588
        internal void CopyFrom(ReadOnlySpan<T> span)
589
        {
590
            CopyFrom( span, FirstIndex );
22✔
591
        }
22✔
592

593
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
594
        internal void CopyFrom(ReadOnlySpan<T> span, int startIndex)
595
        {
596
            var first = OffsetFirstIndex( startIndex );
55✔
597
            CopyFrom( span, first );
55✔
598
        }
55✔
599

600
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
601
        internal Node Push(T item)
602
        {
603
            var node = Expand( 1 );
123✔
604
            var segment = node.GetAbsoluteSegment( node.LastIndex.Segment );
123✔
605
            segment[node.LastIndex.Element] = item;
123✔
606
            return node;
123✔
607
        }
608

609
        internal Node Expand(int length)
610
        {
611
            Assume.IsGreaterThan( length, 0 );
612

613
            if ( Next is null )
138✔
614
            {
615
                Length += length;
126✔
616
                LastIndex = Pool.OffsetIndex( LastIndex, length );
126✔
617
                Pool.AllocateSegments( LastIndex.Segment );
126✔
618
                return this;
126✔
619
            }
620

621
            if ( ! Next.IsReusable || Next.Length < length )
12✔
622
            {
623
                var node = Pool.RentNode( Length + length );
3✔
624
                new RentedMemorySequence<T>( this ).CopyTo( new RentedMemorySequence<T>( node ) );
3✔
625
                Free();
3✔
626
                return node;
3✔
627
            }
628

629
            if ( Next.Length > length )
9✔
630
                Next.DecreaseFragmentedLength( length );
5✔
631
            else
632
            {
633
                Pool.RemoveFromFragmentationHeap( Next );
4✔
634

635
                var next = Next.Next;
4✔
636
                Assume.IsNotNull( next );
637

638
                Next.Next = null;
4✔
639
                Next.Prev = null;
4✔
640
                Next.Deactivate();
4✔
641

642
                Next = next;
4✔
643
                next.Prev = this;
4✔
644
            }
645

646
            Length += length;
9✔
647
            LastIndex = Pool.OffsetIndex( LastIndex, length );
9✔
648
            return this;
9✔
649
        }
650

651
        internal void Sort(int startIndex, int length, Comparison<T> comparer)
652
        {
653
            if ( length < 2 )
7✔
654
                return;
3✔
655

656
            var firstIndex = OffsetFirstIndex( startIndex );
4✔
657
            ref var firstElementRef = ref GetElementRef( firstIndex );
4✔
658

659
            for ( var i = (length - 1) >> 1; i >= 0; --i )
30✔
660
                SortFixDown( firstIndex, length, i, comparer );
11✔
661

662
            while ( length > 1 )
21✔
663
            {
664
                var lastIndex = Pool.OffsetIndex( firstIndex, --length );
17✔
665
                ref var lastElementRef = ref GetElementRef( lastIndex );
17✔
666
                (firstElementRef, lastElementRef) = (lastElementRef, firstElementRef);
17✔
667
                SortFixDown( firstIndex, length, 0, comparer );
17✔
668
            }
669
        }
4✔
670

671
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
672
        internal void DeactivateFragmentationIndex()
673
        {
674
            Assume.IsGreaterThanOrEqualTo( FragmentationIndex, 0 );
675
            FragmentationIndex = InactiveFragmentationIndex;
42✔
676
        }
42✔
677

678
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
679
        internal void UpdateFragmentationIndex(int index)
680
        {
681
            Assume.IsGreaterThanOrEqualTo( index, 0 );
682
            FragmentationIndex = index;
81✔
683
        }
81✔
684

685
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
686
        internal void SwapFragmentationIndex(Node other)
687
        {
688
            Assume.IsGreaterThanOrEqualTo( FragmentationIndex, 0 );
689
            Assume.IsGreaterThanOrEqualTo( other.FragmentationIndex, 0 );
690
            (FragmentationIndex, other.FragmentationIndex) = (other.FragmentationIndex, FragmentationIndex);
31✔
691
        }
31✔
692

693
        internal void Free()
694
        {
695
            if ( IsReusable )
1,540✔
696
                return;
1✔
697

698
            if ( Next is null )
1,539✔
699
                FreeAsTail();
1,435✔
700
            else if ( Prev is null )
104✔
701
                FreeAsHead();
40✔
702
            else
703
                FreeAsIntermediate();
64✔
704
        }
64✔
705

706
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
707
        private static Node GetOrCreateNode(MemorySequencePool<T> pool)
708
        {
709
            if ( ! pool._nodeCache.TryPop( out var node ) )
2,337✔
710
                return new Node( pool );
2,273✔
711

712
            Assume.Equals( node.FragmentationIndex, CachedFragmentationIndex );
713
            Assume.Equals( node.Length, 0 );
714
            Assume.IsNull( node.Prev );
715
            Assume.IsNull( node.Next );
716

717
            node.FragmentationIndex = InactiveFragmentationIndex;
64✔
718
            return node;
64✔
719
        }
720

721
        private void FreeAsTail()
722
        {
723
            Assume.IsNull( Next );
724

725
            if ( Prev is null )
1,435✔
726
                Pool._tailNode = null;
1,298✔
727
            else
728
            {
729
                if ( ! Prev.IsReusable )
137✔
730
                {
731
                    Pool._tailNode = Prev;
126✔
732
                    Prev.Next = null;
126✔
733
                }
734
                else
735
                {
736
                    Pool.RemoveFromFragmentationHeap( Prev );
11✔
737
                    Pool._tailNode = Prev.Prev;
11✔
738

739
                    if ( Prev.Prev is not null )
11✔
740
                    {
741
                        Assume.False( Prev.Prev.IsReusable );
742
                        Prev.Prev.Next = null;
5✔
743
                        Prev.Prev = null;
5✔
744
                    }
745

746
                    Prev.Next = null;
11✔
747
                    Prev.Deactivate();
11✔
748
                }
749

750
                Prev = null;
137✔
751
            }
752

753
            Deactivate();
1,435✔
754
        }
1,435✔
755

756
        private void FreeAsHead()
757
        {
758
            Assume.IsNotNull( Next );
759
            Assume.IsNull( Prev );
760

761
            if ( ! Next.IsReusable )
40✔
762
            {
763
                if ( Length > 0 )
33✔
764
                {
765
                    if ( Pool.ClearReturnedSequences )
32✔
766
                        ClearSegments( FirstIndex, LastIndex );
11✔
767

768
                    Pool.AddToFragmentationHeap( this );
32✔
769
                    return;
32✔
770
                }
771

772
                Next.Prev = null;
1✔
773
            }
774
            else
775
            {
776
                Next.Prev = null;
7✔
777
                Next.PrependFragmentedLength( Length, FirstIndex );
7✔
778
            }
779

780
            Next = null;
8✔
781
            Deactivate();
8✔
782
        }
8✔
783

784
        private void FreeAsIntermediate()
785
        {
786
            Assume.IsNotNull( Next );
787
            Assume.IsNotNull( Prev );
788

789
            if ( ! Next.IsReusable )
64✔
790
            {
791
                if ( ! Prev.IsReusable )
45✔
792
                {
793
                    if ( Length > 0 )
42✔
794
                    {
795
                        if ( Pool.ClearReturnedSequences )
41✔
796
                            ClearSegments( FirstIndex, LastIndex );
16✔
797

798
                        Pool.AddToFragmentationHeap( this );
41✔
799
                        return;
41✔
800
                    }
801

802
                    Prev.Next = Next;
1✔
803
                    Next.Prev = Prev;
1✔
804
                }
805
                else
806
                {
807
                    Prev.Next = Next;
3✔
808
                    Next.Prev = Prev;
3✔
809
                    Prev.AppendFragmentedLength( Length, LastIndex );
3✔
810
                }
811
            }
812
            else if ( ! Prev.IsReusable )
19✔
813
            {
814
                Prev.Next = Next;
15✔
815
                Next.Prev = Prev;
15✔
816
                Next.PrependFragmentedLength( Length, FirstIndex );
15✔
817
            }
818
            else
819
            {
820
                Assume.IsNotNull( Next.Next );
821
                Assume.False( Next.Next.IsReusable );
822
                Pool.RemoveFromFragmentationHeap( Next );
4✔
823

824
                Prev.Next = Next.Next;
4✔
825
                Next.Next.Prev = Prev;
4✔
826
                Prev.AppendFragmentedLength( Length + Next.Length, Next.LastIndex );
4✔
827

828
                Next.Next = null;
4✔
829
                Next.Prev = null;
4✔
830
                Next.Deactivate();
4✔
831
            }
832

833
            Next = null;
23✔
834
            Prev = null;
23✔
835
            Deactivate();
23✔
836
        }
23✔
837

838
        private void ClearSegments(ArraySequenceIndex first, ArraySequenceIndex last)
839
        {
840
            if ( first.Segment == last.Segment )
263✔
841
            {
842
                Array.Clear( GetAbsoluteSegment( first.Segment ), first.Element, last.Element - first.Element + 1 );
209✔
843
                return;
209✔
844
            }
845

846
            Array.Clear( GetAbsoluteSegment( first.Segment ), first.Element, Pool.SegmentLength - first.Element );
54✔
847

848
            for ( var i = first.Segment + 1; i < last.Segment; ++i )
172✔
849
                Array.Clear( GetAbsoluteSegment( i ) );
32✔
850

851
            Array.Clear( GetAbsoluteSegment( last.Segment ), 0, last.Element + 1 );
54✔
852
        }
54✔
853

854
        [Pure]
855
        private int IndexOf(T item, ArraySequenceIndex first, ArraySequenceIndex last)
856
        {
857
            int index;
858
            if ( first.Segment == last.Segment )
60✔
859
            {
860
                index = Array.IndexOf( GetAbsoluteSegment( first.Segment ), item, first.Element, last.Element - first.Element + 1 );
16✔
861
                return index != -1 ? index - first.Element : index;
16✔
862
            }
863

864
            var firstSegmentLength = Pool.SegmentLength - first.Element;
44✔
865
            index = Array.IndexOf( GetAbsoluteSegment( first.Segment ), item, first.Element, firstSegmentLength );
44✔
866
            if ( index != -1 )
44✔
867
                return index - first.Element;
10✔
868

869
            var secondSegmentIndex = first.Segment + 1;
34✔
870
            for ( var i = secondSegmentIndex; i < last.Segment; ++i )
104✔
871
            {
872
                index = Array.IndexOf( GetAbsoluteSegment( i ), item );
34✔
873
                if ( index != -1 )
34✔
874
                    return ((i - secondSegmentIndex) << Pool._segmentLengthLog2) + firstSegmentLength + index;
16✔
875
            }
876

877
            index = Array.IndexOf( GetAbsoluteSegment( last.Segment ), item, 0, last.Element + 1 );
18✔
878
            return index != -1 ? ((last.Segment - secondSegmentIndex) << Pool._segmentLengthLog2) + firstSegmentLength + index : index;
18✔
879
        }
880

881
        private void CopyTo(Span<T> span, ArraySequenceIndex first, ArraySequenceIndex last)
882
        {
883
            if ( first.Segment == last.Segment )
120✔
884
            {
885
                GetAbsoluteSegment( first.Segment ).AsSpan( first.Element, last.Element - first.Element + 1 ).CopyTo( span );
60✔
886
                return;
60✔
887
            }
888

889
            GetAbsoluteSegment( first.Segment ).AsSpan( first.Element ).CopyTo( span );
60✔
890
            span = span.Slice( Pool.SegmentLength - first.Element );
60✔
891

892
            for ( var i = first.Segment + 1; i < last.Segment; ++i )
310✔
893
            {
894
                GetAbsoluteSegment( i ).CopyTo( span );
95✔
895
                span = span.Slice( Pool.SegmentLength );
95✔
896
            }
897

898
            GetAbsoluteSegment( last.Segment ).AsSpan( 0, last.Element + 1 ).CopyTo( span );
60✔
899
        }
60✔
900

901
        private void CopyFrom(ReadOnlySpan<T> span, ArraySequenceIndex first)
902
        {
903
            if ( span.Length == 0 )
77✔
904
                return;
1✔
905

906
            var segment = GetAbsoluteSegment( first.Segment ).AsSpan( first.Element );
76✔
907
            if ( segment.Length >= span.Length )
76✔
908
            {
909
                span.CopyTo( segment );
37✔
910
                return;
37✔
911
            }
912

913
            span.Slice( 0, segment.Length ).CopyTo( segment );
39✔
914
            span = span.Slice( segment.Length );
39✔
915
            var segmentIndex = first.Segment + 1;
39✔
916
            segment = GetAbsoluteSegment( segmentIndex );
39✔
917

918
            while ( segment.Length < span.Length )
54✔
919
            {
920
                span.Slice( 0, segment.Length ).CopyTo( segment );
15✔
921
                span = span.Slice( segment.Length );
15✔
922
                segment = GetAbsoluteSegment( ++segmentIndex );
15✔
923
            }
924

925
            span.CopyTo( segment );
39✔
926
        }
39✔
927

928
        private void SortFixDown(ArraySequenceIndex firstIndex, int length, int parentIndex, Comparison<T> comparer)
929
        {
930
            var leftIndex = (parentIndex << 1) + 1;
28✔
931

932
            while ( leftIndex < length )
54✔
933
            {
934
                var index = Pool.OffsetIndex( firstIndex, parentIndex );
33✔
935
                ref var parentRef = ref GetElementRef( index );
33✔
936

937
                var swapIndex = leftIndex;
33✔
938
                index = Pool.OffsetIndex( firstIndex, swapIndex );
33✔
939
                ref var swapRef = ref GetElementRef( index );
33✔
940

941
                if ( comparer( swapRef, parentRef ) <= 0 )
33✔
942
                {
943
                    swapIndex = parentIndex;
7✔
944
                    swapRef = ref parentRef;
7✔
945
                }
946

947
                var rightIndex = leftIndex + 1;
33✔
948
                if ( rightIndex < length )
33✔
949
                {
950
                    index = Pool.OffsetIndex( firstIndex, rightIndex );
23✔
951
                    ref var rightRef = ref GetElementRef( index );
23✔
952

953
                    if ( comparer( rightRef, swapRef ) > 0 )
23✔
954
                    {
955
                        swapIndex = rightIndex;
4✔
956
                        swapRef = ref rightRef;
4✔
957
                    }
958
                }
959

960
                if ( swapIndex == parentIndex )
33✔
961
                    break;
962

963
                (parentRef, swapRef) = (swapRef, parentRef);
26✔
964
                parentIndex = swapIndex;
26✔
965
                leftIndex = (parentIndex << 1) + 1;
26✔
966
            }
967
        }
28✔
968

969
        [Pure]
970
        [MethodImpl( MethodImplOptions.AggressiveInlining )]
971
        private ref T GetElementRef(ArraySequenceIndex index)
972
        {
973
            return ref Unsafe.Add( ref MemoryMarshal.GetArrayDataReference( GetAbsoluteSegment( index.Segment ) ), index.Element );
110✔
974
        }
975

976
        private void Deactivate()
977
        {
978
            Assume.Equals( FragmentationIndex, InactiveFragmentationIndex );
979
            Assume.IsNull( Prev );
980
            Assume.IsNull( Next );
981

982
            Pool._nodeCache.Push( this );
1,485✔
983
            if ( Pool.ClearReturnedSequences )
1,485✔
984
                ClearSegments();
1,312✔
985

986
            FragmentationIndex = CachedFragmentationIndex;
1,485✔
987
            FirstIndex = ArraySequenceIndex.Zero;
1,485✔
988
            LastIndex = Pool.GetMinusOneIndex();
1,485✔
989
            Length = 0;
1,485✔
990
        }
1,485✔
991

992
        private void DecreaseFragmentedLength(int offset)
993
        {
994
            Assume.IsInExclusiveRange( offset, 0, Length );
995
            Assume.IsGreaterThanOrEqualTo( FragmentationIndex, 0 );
996

997
            FirstIndex = OffsetFirstIndex( offset );
22✔
998
            Length -= offset;
22✔
999
            AssumeLastIndexValidity();
1000
            Pool.FixDownFragmentationHeap( this );
22✔
1001
        }
22✔
1002

1003
        private void PrependFragmentedLength(int offset, ArraySequenceIndex firstIndex)
1004
        {
1005
            if ( offset == 0 )
22✔
1006
                return;
2✔
1007

1008
            Assume.IsGreaterThan( offset, 0 );
1009
            Assume.IsGreaterThanOrEqualTo( FragmentationIndex, 0 );
1010

1011
            Length += offset;
20✔
1012
            FirstIndex = firstIndex;
20✔
1013
            AssumeLastIndexValidity();
1014
            Pool.FixUpFragmentationHeap( this );
20✔
1015
        }
20✔
1016

1017
        private void AppendFragmentedLength(int offset, ArraySequenceIndex lastIndex)
1018
        {
1019
            if ( offset == 0 )
7✔
1020
                return;
1✔
1021

1022
            Assume.IsGreaterThan( offset, 0 );
1023
            Assume.IsGreaterThanOrEqualTo( FragmentationIndex, 0 );
1024

1025
            Length += offset;
6✔
1026
            LastIndex = lastIndex;
6✔
1027
            AssumeLastIndexValidity();
1028
            Pool.FixUpFragmentationHeap( this );
6✔
1029
        }
6✔
1030

1031
        [Conditional( "DEBUG" )]
1032
        private void AssumeLastIndexValidity()
1033
        {
1034
            var expected = OffsetFirstIndex( Length - 1 );
×
1035
            Assume.Equals( LastIndex.Segment, expected.Segment );
1036
            Assume.Equals( LastIndex.Element, expected.Element );
1037
        }
×
1038
    }
1039
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc