| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | using System.Buffers; |
| | 4 | | using System.Diagnostics; |
| | 5 | |
|
| | 6 | | namespace IceRpc.Internal; |
| | 7 | |
|
| | 8 | | /// <summary>Represents a coupler that concatenates two <see cref="ReadOnlySequence{T} "/> of byte to form a single |
| | 9 | | /// sequence.</summary> |
| | 10 | | /// <remarks>This class does not copy the memory of the sequences it concatenates. It does however create a |
| | 11 | | /// ReadOnlySequenceSegment instance for each segment of the input sequences, so it's not ultra cheap. If performance is |
| | 12 | | /// a concern, you should reuse the same sequence coupler over and over as it reuses the ReadOnlySequenceSegment |
| | 13 | | /// instances it creates.</remarks> |
| | 14 | | internal sealed class SequenceCoupler |
| | 15 | | { |
| 374 | 16 | | private readonly Segment _head = new(); |
| | 17 | |
|
| | 18 | | internal ReadOnlySequence<byte> Concat(ReadOnlySequence<byte> first, ReadOnlySequence<byte> second) |
| 5648 | 19 | | { |
| 5648 | 20 | | if (first.IsEmpty) |
| 4 | 21 | | { |
| 4 | 22 | | return second; |
| | 23 | | } |
| 5644 | 24 | | if (second.IsEmpty) |
| 3573 | 25 | | { |
| 3573 | 26 | | return first; |
| | 27 | | } |
| | 28 | |
|
| 2071 | 29 | | Segment? tail = null; |
| 2071 | 30 | | Segment next = _head; |
| 2071 | 31 | | long runningIndex = 0; |
| | 32 | |
|
| 2071 | 33 | | Append(first); |
| 2071 | 34 | | Append(second); |
| | 35 | |
|
| 2071 | 36 | | Debug.Assert(tail is not null); |
| 2071 | 37 | | return new ReadOnlySequence<byte>(_head, startIndex: 0, tail, endIndex: tail.Memory.Length); |
| | 38 | |
|
| | 39 | | void Append(ReadOnlySequence<byte> sequence) |
| 4142 | 40 | | { |
| 20818 | 41 | | foreach (ReadOnlyMemory<byte> memory in sequence) |
| 4196 | 42 | | { |
| 4196 | 43 | | tail = next; |
| 4196 | 44 | | tail.Reset(memory, runningIndex); |
| 4196 | 45 | | runningIndex += memory.Length; |
| | 46 | |
|
| | 47 | | // We always get (and possibly create) one extra segment. It's not used if we've reached the last |
| | 48 | | // segment of second. |
| 4196 | 49 | | next = tail.GetNext(); |
| 4196 | 50 | | } |
| 4142 | 51 | | } |
| 5648 | 52 | | } |
| | 53 | |
|
| | 54 | | private class Segment : ReadOnlySequenceSegment<byte> |
| | 55 | | { |
| | 56 | | // GetNext always returns the next segment, and creates one if needed. |
| | 57 | | // Note that we never clean-up these segments. |
| | 58 | | internal Segment GetNext() |
| 4196 | 59 | | { |
| 4196 | 60 | | if (Next is not Segment next) |
| 112 | 61 | | { |
| 112 | 62 | | next = new Segment(); |
| 112 | 63 | | Next = next; |
| 112 | 64 | | } |
| 4196 | 65 | | return next; |
| 4196 | 66 | | } |
| | 67 | |
|
| | 68 | | internal void Reset(ReadOnlyMemory<byte> memory, long runningIndex) |
| 4196 | 69 | | { |
| 4196 | 70 | | Memory = memory; |
| 4196 | 71 | | RunningIndex = runningIndex; |
| 4196 | 72 | | } |
| | 73 | | } |
| | 74 | | } |