using System;
using System.Buffers;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Ryujinx.Common.Extensions
{
public static class SequenceReaderExtensions
{
///
/// Dumps the entire to a file, restoring its previous location afterward.
/// Useful for debugging purposes.
///
/// The to write to a file
/// The path and name of the file to create and dump to
public static void DumpToFile(this ref SequenceReader reader, string fileFullName)
{
var initialConsumed = reader.Consumed;
reader.Rewind(initialConsumed);
using (var fileStream = System.IO.File.Create(fileFullName, 4096, System.IO.FileOptions.None))
{
while (reader.End == false)
{
var span = reader.CurrentSpan;
fileStream.Write(span);
reader.Advance(span.Length);
}
}
reader.SetConsumed(initialConsumed);
}
///
/// Returns a reference to the desired value. This ref should always be used. The argument passed in should never be used, as this is only used for storage if the value
/// must be copied from multiple segments held by the .
///
/// Type to get
/// The to read from
/// A location used as storage if (and only if) the value to be read spans multiple segments
/// A reference to the desired value, either directly to memory in the , or to if it has been used for copying the value in to
///
/// DO NOT use after calling this method, as it will only
/// contain a value if the value couldn't be referenced directly because it spans multiple segments.
/// To discourage use, it is recommended to call this method like the following:
///
/// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
///
///
/// The does not contain enough data to read a value of type
public static ref readonly T GetRefOrRefToCopy(this scoped ref SequenceReader reader, out T copyDestinationIfRequiredDoNotUse) where T : unmanaged
{
int lengthRequired = Unsafe.SizeOf();
ReadOnlySpan span = reader.UnreadSpan;
if (lengthRequired <= span.Length)
{
reader.Advance(lengthRequired);
copyDestinationIfRequiredDoNotUse = default;
ReadOnlySpan spanOfT = MemoryMarshal.Cast(span);
return ref spanOfT[0];
}
else
{
copyDestinationIfRequiredDoNotUse = default;
Span valueSpan = MemoryMarshal.CreateSpan(ref copyDestinationIfRequiredDoNotUse, 1);
Span valueBytesSpan = MemoryMarshal.AsBytes(valueSpan);
if (!reader.TryCopyTo(valueBytesSpan))
{
throw new ArgumentOutOfRangeException(nameof(reader), "The sequence is not long enough to read the desired value.");
}
reader.Advance(lengthRequired);
return ref valueSpan[0];
}
}
///
/// Reads an as little endian.
///
/// The to read from
/// A location to receive the read value
/// Thrown if there wasn't enough data for an
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadLittleEndian(this ref SequenceReader reader, out int value)
{
if (!reader.TryReadLittleEndian(out value))
{
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
}
}
///
/// Reads the desired unmanaged value by copying it to the specified .
///
/// Type to read
/// The to read from
/// The target that will receive the read value
/// The does not contain enough data to read a value of type
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadUnmanaged(this ref SequenceReader reader, out T value) where T : unmanaged
{
if (!reader.TryReadUnmanaged(out value))
{
throw new ArgumentOutOfRangeException(nameof(value), "The sequence is not long enough to read the desired value.");
}
}
///
/// Sets the reader's position as bytes consumed.
///
/// The to set the position
/// The number of bytes consumed
public static void SetConsumed(ref this SequenceReader reader, long consumed)
{
reader.Rewind(reader.Consumed);
reader.Advance(consumed);
}
///
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
/// structs - see remarks for full details.
///
/// Type to read
///
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
/// overloads such as
///
///
/// True if successful. will be default if failed (due to lack of space).
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool TryReadUnmanaged(ref this SequenceReader reader, out T value) where T : unmanaged
{
ReadOnlySpan span = reader.UnreadSpan;
if (span.Length < sizeof(T))
{
return TryReadUnmanagedMultiSegment(ref reader, out value);
}
value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(span));
reader.Advance(sizeof(T));
return true;
}
private static unsafe bool TryReadUnmanagedMultiSegment(ref SequenceReader reader, out T value) where T : unmanaged
{
Debug.Assert(reader.UnreadSpan.Length < sizeof(T));
// Not enough data in the current segment, try to peek for the data we need.
T buffer = default;
Span tempSpan = new Span(&buffer, sizeof(T));
if (!reader.TryCopyTo(tempSpan))
{
value = default;
return false;
}
value = Unsafe.ReadUnaligned(ref MemoryMarshal.GetReference(tempSpan));
reader.Advance(sizeof(T));
return true;
}
}
}