SequenceReaderExtensions.cs
1 using System; 2 using System.Buffers; 3 using System.Diagnostics; 4 using System.Runtime.CompilerServices; 5 using System.Runtime.InteropServices; 6 7 namespace Ryujinx.Common.Extensions 8 { 9 public static class SequenceReaderExtensions 10 { 11 /// <summary> 12 /// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward. 13 /// Useful for debugging purposes. 14 /// </summary> 15 /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to write to a file</param> 16 /// <param name="fileFullName">The path and name of the file to create and dump to</param> 17 public static void DumpToFile(this ref SequenceReader<byte> reader, string fileFullName) 18 { 19 var initialConsumed = reader.Consumed; 20 21 reader.Rewind(initialConsumed); 22 23 using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None)) 24 { 25 while (reader.End == false) 26 { 27 var span = reader.CurrentSpan; 28 fileStream.Write(span); 29 reader.Advance(span.Length); 30 } 31 } 32 33 reader.SetConsumed(initialConsumed); 34 } 35 36 /// <summary> 37 /// Returns a reference to the desired value. This ref should always be used. The argument passed in <paramref name="copyDestinationIfRequiredDoNotUse"/> should never be used, as this is only used for storage if the value 38 /// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>. 39 /// </summary> 40 /// <typeparam name="T">Type to get</typeparam> 41 /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param> 42 /// <param name="copyDestinationIfRequiredDoNotUse">A location used as storage if (and only if) the value to be read spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments</param> 43 /// <returns>A reference to the desired value, either directly to memory in the <see cref="SequenceReader{Byte}"/>, or to <paramref name="copyDestinationIfRequiredDoNotUse"/> if it has been used for copying the value in to</returns> 44 /// <remarks> 45 /// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only 46 /// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments. 47 /// To discourage use, it is recommended to call this method like the following: 48 /// <c> 49 /// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _); 50 /// </c> 51 /// </remarks> 52 /// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception> 53 public static ref readonly T GetRefOrRefToCopy<T>(this scoped ref SequenceReader<byte> reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged 54 { 55 int lengthRequired = Unsafe.SizeOf<T>(); 56 57 ReadOnlySpan<byte> span = reader.UnreadSpan; 58 if (lengthRequired <= span.Length) 59 { 60 reader.Advance(lengthRequired); 61 62 copyDestinationIfRequiredDoNotUse = default; 63 64 ReadOnlySpan<T> spanOfT = MemoryMarshal.Cast<byte, T>(span); 65 66 return ref spanOfT[0]; 67 } 68 else 69 { 70 copyDestinationIfRequiredDoNotUse = default; 71 72 Span<T> valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1); 73 74 Span<byte> valueBytesSpan = MemoryMarshal.AsBytes(valueSpan); 75 76 if (!reader.TryCopyTo(valueBytesSpan)) 77 { 78 throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value."); 79 } 80 81 reader.Advance(lengthRequired); 82 83 return ref valueSpan[0]; 84 } 85 } 86 87 /// <summary> 88 /// Reads an <see cref="int"/> as little endian. 89 /// </summary> 90 /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param> 91 /// <param name="value">A location to receive the read value</param> 92 /// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception> 93 [MethodImpl(MethodImplOptions.AggressiveInlining)] 94 public static void ReadLittleEndian(this ref SequenceReader<byte> reader, out int value) 95 { 96 if (!reader.TryReadLittleEndian(out value)) 97 { 98 throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); 99 } 100 } 101 102 /// <summary> 103 /// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>. 104 /// </summary> 105 /// <typeparam name="T">Type to read</typeparam> 106 /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param> 107 /// <param name="value">The target that will receive the read value</param> 108 /// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception> 109 [MethodImpl(MethodImplOptions.AggressiveInlining)] 110 public static void ReadUnmanaged<T>(this ref SequenceReader<byte> reader, out T value) where T : unmanaged 111 { 112 if (!reader.TryReadUnmanaged(out value)) 113 { 114 throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value."); 115 } 116 } 117 118 /// <summary> 119 /// Sets the reader's position as bytes consumed. 120 /// </summary> 121 /// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param> 122 /// <param name="consumed">The number of bytes consumed</param> 123 public static void SetConsumed(ref this SequenceReader<byte> reader, long consumed) 124 { 125 reader.Rewind(reader.Consumed); 126 reader.Advance(consumed); 127 } 128 129 /// <summary> 130 /// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary 131 /// structs - see remarks for full details. 132 /// </summary> 133 /// <typeparam name="T">Type to read</typeparam> 134 /// <remarks> 135 /// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to 136 /// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit 137 /// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/> 138 /// </remarks> 139 /// <returns> 140 /// True if successful. <paramref name="value"/> will be default if failed (due to lack of space). 141 /// </returns> 142 [MethodImpl(MethodImplOptions.AggressiveInlining)] 143 public static unsafe bool TryReadUnmanaged<T>(ref this SequenceReader<byte> reader, out T value) where T : unmanaged 144 { 145 ReadOnlySpan<byte> span = reader.UnreadSpan; 146 147 if (span.Length < sizeof(T)) 148 { 149 return TryReadUnmanagedMultiSegment(ref reader, out value); 150 } 151 152 value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span)); 153 154 reader.Advance(sizeof(T)); 155 156 return true; 157 } 158 159 private static unsafe bool TryReadUnmanagedMultiSegment<T>(ref SequenceReader<byte> reader, out T value) where T : unmanaged 160 { 161 Debug.Assert(reader.UnreadSpan.Length < sizeof(T)); 162 163 // Not enough data in the current segment, try to peek for the data we need. 164 T buffer = default; 165 166 Span<byte> tempSpan = new Span<byte>(&buffer, sizeof(T)); 167 168 if (!reader.TryCopyTo(tempSpan)) 169 { 170 value = default; 171 return false; 172 } 173 174 value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan)); 175 176 reader.Advance(sizeof(T)); 177 178 return true; 179 } 180 } 181 }