Updates span extensions (#276)

This commit is contained in:
Kamron Batman 2020-10-17 21:14:03 -07:00 • committed by GitHub
parent a87d0e2d70
commit 4211835b5d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 123 additions and 709 deletions

View file

@ -1,331 +0,0 @@
// Copyright (c) Harry Pierson. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Threading;
namespace System.Buffers
{
public ref struct BufferReader<T>
{
private readonly bool usingSequence;
private readonly ReadOnlySequence<T> sequence;
private SequencePosition currentPosition;
private SequencePosition nextPosition;
private bool moreData;
private readonly long length;
public BufferReader(ReadOnlySpan<T> span)
{
usingSequence = false;
CurrentSpanIndex = 0;
Consumed = 0;
sequence = default;
currentPosition = default;
length = span.Length;
CurrentSpan = span;
nextPosition = default;
moreData = span.Length > 0;
}
public BufferReader(in ReadOnlySequence<T> sequence)
{
usingSequence = true;
CurrentSpanIndex = 0;
Consumed = 0;
this.sequence = sequence;
currentPosition = sequence.Start;
length = -1;
var first = sequence.First.Span;
CurrentSpan = first;
nextPosition = sequence.GetPosition(first.Length);
moreData = first.Length > 0;
if (!moreData && !sequence.IsSingleSegment)
{
moreData = true;
GetNextSpan();
}
}
public readonly bool End => !moreData;
public ReadOnlySpan<T> CurrentSpan { get; private set; }
public int CurrentSpanIndex { get; private set; }
public readonly ReadOnlySpan<T> UnreadSpan
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => CurrentSpan.Slice(CurrentSpanIndex);
}
public long Consumed { get; private set; }
public readonly long Remaining => Length - Consumed;
public readonly long Length
{
get
{
if (length < 0)
// Cast-away readonly to initialize lazy field
{
Volatile.Write(ref Unsafe.AsRef(length), sequence.Length);
}
return length;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryPeek([MaybeNullWhen(false)] out T value)
{
if (moreData)
{
value = CurrentSpan[CurrentSpanIndex];
return true;
}
value = default!;
return false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryRead([MaybeNullWhen(false)] out T value)
{
if (End)
{
value = default!;
return false;
}
value = CurrentSpan[CurrentSpanIndex];
CurrentSpanIndex++;
Consumed++;
if (CurrentSpanIndex >= CurrentSpan.Length)
{
if (usingSequence)
{
GetNextSpan();
}
else
{
moreData = false;
}
}
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Rewind(long count)
{
if ((ulong)count > (ulong)Consumed)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
Consumed -= count;
if (CurrentSpanIndex >= count)
{
CurrentSpanIndex -= (int)count;
moreData = true;
}
else if (usingSequence)
{
// Current segment doesn't have enough data, scan backward through segments
RetreatToPreviousSpan(Consumed);
}
else
{
throw new ArgumentOutOfRangeException(
nameof(count),
$"Rewind went past the start of the memory by {count}."
);
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void RetreatToPreviousSpan(long consumed)
{
ResetReader();
Advance(consumed);
}
private void ResetReader()
{
CurrentSpanIndex = 0;
Consumed = 0;
currentPosition = sequence.Start;
nextPosition = currentPosition;
if (sequence.TryGet(ref nextPosition, out var memory))
{
moreData = true;
if (memory.Length == 0)
{
CurrentSpan = default;
// No data in the first span, move to one with data
GetNextSpan();
}
else
{
CurrentSpan = memory.Span;
}
}
else
{
// No data in any spans and at end of sequence
moreData = false;
CurrentSpan = default;
}
}
private void GetNextSpan()
{
if (!sequence.IsSingleSegment)
{
var previousNextPosition = nextPosition;
while (sequence.TryGet(ref nextPosition, out var memory))
{
currentPosition = previousNextPosition;
if (memory.Length > 0)
{
CurrentSpan = memory.Span;
CurrentSpanIndex = 0;
return;
}
CurrentSpan = default;
CurrentSpanIndex = 0;
previousNextPosition = nextPosition;
}
}
moreData = false;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(long count)
{
const long TooBigOrNegative = unchecked((long)0xFFFFFFFF80000000);
if ((count & TooBigOrNegative) == 0 && CurrentSpan.Length - CurrentSpanIndex > (int)count)
{
CurrentSpanIndex += (int)count;
Consumed += count;
}
else if (usingSequence)
{
// Can't satisfy from the current span
AdvanceToNextSpan(count);
}
else if (CurrentSpan.Length - CurrentSpanIndex == (int)count)
{
CurrentSpanIndex += (int)count;
Consumed += count;
moreData = false;
}
else
{
throw new ArgumentOutOfRangeException(nameof(count));
}
}
private void AdvanceToNextSpan(long count)
{
if (count < 0)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
Consumed += count;
while (moreData)
{
var remaining = CurrentSpan.Length - CurrentSpanIndex;
if (remaining > count)
{
CurrentSpanIndex += (int)count;
count = 0;
break;
}
// As there may not be any further segments we need to
// push the current index to the end of the span.
CurrentSpanIndex += remaining;
count -= remaining;
GetNextSpan();
if (count == 0)
{
break;
}
}
if (count != 0)
{
// Not enough data left- adjust for where we actually ended and throw
Consumed -= count;
throw new ArgumentOutOfRangeException(nameof(count));
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public readonly bool TryCopyTo(Span<T> destination)
{
// This API doesn't advance to facilitate conditional advancement based on the data returned.
// We don't provide an advance option to allow easier utilizing of stack allocated destination spans.
// (Because we can make this method readonly we can guarantee that we won't capture the span.)
var firstSpan = UnreadSpan;
if (firstSpan.Length >= destination.Length)
{
firstSpan.Slice(0, destination.Length).CopyTo(destination);
return true;
}
// Not enough in the current span to satisfy the request, fall through to the slow path
return TryCopyMultisegment(destination);
}
internal readonly bool TryCopyMultisegment(Span<T> destination)
{
// If we don't have enough to fill the requested buffer, return false
if (Remaining < destination.Length)
{
return false;
}
var firstSpan = UnreadSpan;
firstSpan.CopyTo(destination);
var copied = firstSpan.Length;
var next = nextPosition;
while (sequence.TryGet(ref next, out var nextSegment))
{
if (nextSegment.Length > 0)
{
var nextSpan = nextSegment.Span;
var toCopy = Math.Min(nextSpan.Length, destination.Length - copied);
nextSpan.Slice(0, toCopy).CopyTo(destination.Slice(copied));
copied += toCopy;
if (copied >= destination.Length)
{
break;
}
}
}
return true;
}
}
}

View file

@ -1,180 +0,0 @@
// Copyright (c) Harry Pierson. All rights reserved.
// Licensed under the MIT license.
// See LICENSE file in the project root for full license information.
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace System.Buffers
{
public static class BufferReaderExtensions
{
private static unsafe bool TryRead<T>(ref this BufferReader<byte> reader, out T value)
where T : unmanaged
{
var span = reader.UnreadSpan;
if (span.Length < sizeof(T))
{
return TryReadMultisegment(ref reader, out value);
}
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(span));
reader.Advance(sizeof(T));
return true;
}
private static unsafe bool TryReadMultisegment<T>(ref BufferReader<byte> reader, out T value)
where T : unmanaged
{
// Not enough data in the current segment, try to peek for the data we need.
T buffer = default;
var tempSpan = new Span<byte>(&buffer, sizeof(T));
if (!reader.TryCopyTo(tempSpan))
{
value = default;
return false;
}
value = Unsafe.ReadUnaligned<T>(ref MemoryMarshal.GetReference(tempSpan));
reader.Advance(sizeof(T));
return true;
}
public static bool TryRead(ref this BufferReader<byte> reader, out sbyte value)
{
if (TryRead(ref reader, out byte byteValue))
{
value = unchecked((sbyte)byteValue);
return true;
}
value = default;
return false;
}
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out short value) =>
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out short value) =>
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out short value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out ushort value)
{
if (TryReadLittleEndian(ref reader, out short signedvalue))
{
value = unchecked((ushort)signedvalue);
return true;
}
value = default;
return false;
}
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out ushort value)
{
if (TryReadBigEndian(ref reader, out short signedvalue))
{
value = unchecked((ushort)signedvalue);
return true;
}
value = default;
return false;
}
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out int value) =>
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out int value) =>
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out int value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out uint value)
{
if (TryReadLittleEndian(ref reader, out int signedvalue))
{
value = unchecked((uint)signedvalue);
return true;
}
value = default;
return false;
}
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out uint value)
{
if (TryReadBigEndian(ref reader, out int signedvalue))
{
value = unchecked((uint)signedvalue);
return true;
}
value = default;
return false;
}
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out long value) =>
BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out long value) =>
!BitConverter.IsLittleEndian ? reader.TryRead(out value) : TryReadReverseEndianness(ref reader, out value);
private static bool TryReadReverseEndianness(ref BufferReader<byte> reader, out long value)
{
if (reader.TryRead(out value))
{
value = BinaryPrimitives.ReverseEndianness(value);
return true;
}
return false;
}
public static bool TryReadLittleEndian(ref this BufferReader<byte> reader, out ulong value)
{
if (TryReadLittleEndian(ref reader, out long signedvalue))
{
value = unchecked((ulong)signedvalue);
return true;
}
value = default;
return false;
}
public static bool TryReadBigEndian(ref this BufferReader<byte> reader, out ulong value)
{
if (TryReadBigEndian(ref reader, out long signedvalue))
{
value = unchecked((ulong)signedvalue);
return true;
}
value = default;
return false;
}
}
}

View file

@ -1,154 +0,0 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Runtime.CompilerServices;
namespace System.Buffers
{
/// <summary>
/// A fast access struct that wraps <see cref="IBufferWriter{T}" />.
/// </summary>
/// <typeparam name="T">The type of element to be written.</typeparam>
internal ref struct BufferWriter<T> where T : IBufferWriter<byte>
{
/// <summary>
/// The underlying <see cref="IBufferWriter{T}" />.
/// </summary>
private T _output;
/// <summary>
/// The result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)" />, less any bytes already "consumed" with
/// <see cref="Advance(int)" />.
/// Backing field for the <see cref="Span" /> property.
/// </summary>
private Span<byte> _span;
/// <summary>
/// The number of uncommitted bytes (all the calls to <see cref="Advance(int)" /> since the last call to
/// <see cref="Commit" />).
/// </summary>
private int _buffered;
/// <summary>
/// The total number of bytes written with this writer.
/// Backing field for the <see cref="BytesCommitted" /> property.
/// </summary>
private long _bytesCommitted;
/// <summary>
/// Initializes a new instance of the <see cref="BufferWriter{T}" /> struct.
/// </summary>
/// <param name="output">The <see cref="IBufferWriter{T}" /> to be wrapped.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public BufferWriter(T output)
{
_buffered = 0;
_bytesCommitted = 0;
_output = output;
_span = output.GetSpan();
}
/// <summary>
/// Gets the result of the last call to <see cref="IBufferWriter{T}.GetSpan(int)" />.
/// </summary>
public Span<byte> Span => _span;
/// <summary>
/// Gets the total number of bytes written with this writer.
/// </summary>
public long BytesCommitted => _bytesCommitted;
/// <summary>
/// Calls <see cref="IBufferWriter{T}.Advance(int)" /> on the underlying writer
/// with the number of uncommitted bytes.
/// </summary>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Commit()
{
var buffered = _buffered;
if (buffered > 0)
{
_bytesCommitted += buffered;
_buffered = 0;
_output.Advance(buffered);
}
}
/// <summary>
/// Used to indicate that part of the buffer has been written to.
/// </summary>
/// <param name="count">The number of bytes written to.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Advance(int count)
{
_buffered += count;
_span = _span.Slice(count);
}
/// <summary>
/// Copies the caller's buffer into this writer and calls <see cref="Advance(int)" /> with the length of the source buffer.
/// </summary>
/// <param name="source">The buffer to copy in.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Write(ReadOnlySpan<byte> source)
{
if (_span.Length >= source.Length)
{
source.CopyTo(_span);
Advance(source.Length);
}
else
{
WriteMultiBuffer(source);
}
}
/// <summary>
/// Acquires a new buffer if necessary to ensure that some given number of bytes can be written to a single buffer.
/// </summary>
/// <param name="count">The number of bytes that must be allocated in a single buffer.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Ensure(int count = 1)
{
if (_span.Length < count)
{
EnsureMore(count);
}
}
/// <summary>
/// Gets a fresh span to write to, with an optional minimum size.
/// </summary>
/// <param name="count">The minimum size for the next requested buffer.</param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void EnsureMore(int count = 0)
{
if (_buffered > 0)
{
Commit();
}
_span = _output.GetSpan(count);
}
/// <summary>
/// Copies the caller's buffer into this writer, potentially across multiple buffers from the underlying writer.
/// </summary>
/// <param name="source">The buffer to copy into this writer.</param>
private void WriteMultiBuffer(ReadOnlySpan<byte> source)
{
while (source.Length > 0)
{
if (_span.Length == 0)
{
EnsureMore();
}
var writable = Math.Min(source.Length, _span.Length);
source.Slice(0, writable).CopyTo(_span);
source = source.Slice(writable);
Advance(writable);
}
}
}
}

View file

@ -0,0 +1,90 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SpanReadExtensions.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
namespace System.Buffers
{
public static class SpanReadExtensions
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static byte ReadInt8(this Span<byte> span, ref int pos) => span[pos++];
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ReadInt16(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadInt16BigEndian(span.Slice(pos, 2));
pos += 2;
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadUInt16(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadUInt16BigEndian(span.Slice(pos, 2));
pos += 2;
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadInt32(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadInt32BigEndian(span.Slice(pos, 4));
pos += 4;
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadUInt32BigEndian(span.Slice(pos, 4));
pos += 4;
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static short ReadInt16LE(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadInt16LittleEndian(span.Slice(pos, 2));
pos += 2;
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static ushort ReadUInt16LE(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadUInt16LittleEndian(span.Slice(pos, 2));
pos += 2;
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int ReadInt32LE(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadInt32LittleEndian(span.Slice(pos, 4));
pos += 4;
return v;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static uint ReadUInt32LE(this Span<byte> span, ref int pos)
{
var v = BinaryPrimitives.ReadUInt32LittleEndian(span.Slice(pos, 4));
pos += 4;
return v;
}
}
}

View file

@ -1,3 +1,18 @@
/*************************************************************************
* ModernUO *
* Copyright 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: SpanWriteExtensions.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System.Buffers.Binary;
using System.Runtime.CompilerServices;
using System.Text;
@ -5,7 +20,7 @@ using Server;
namespace System.Buffers
{
public static class SpanExtensions
public static class SpanWriteExtensions
{
// Extensions
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@ -137,7 +152,7 @@ namespace System.Buffers
var length = value.Length;
pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length));
#if NO_LOCAL_INIT
span[pos] = 0; // Null terminator
span[pos] = 0; // Null terminator
#endif
pos++;
}
@ -149,7 +164,7 @@ namespace System.Buffers
pos += Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length));
#if NO_LOCAL_INIT
span[pos] = 0; // Null terminator
span[pos] = 0; // Null terminator
#endif
pos++;
}
@ -159,10 +174,10 @@ namespace System.Buffers
{
var length = value.Length <= amount ? value.Length : amount;
#if NO_LOCAL_INIT
int bytesWritten = Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length));
int bytesWritten = Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length));
if (bytesWritten < amount)
span.Slice(pos + bytesWritten, amount - bytesWritten).Clear();
if (bytesWritten < amount)
span.Slice(pos + bytesWritten, amount - bytesWritten).Clear();
#else
Encoding.ASCII.GetBytes(value.AsSpan(0, length), span.Slice(pos, length));
#endif
@ -187,7 +202,7 @@ namespace System.Buffers
{
pos += Encoding.BigEndianUnicode.GetBytes(value, span.Slice(pos));
#if NO_LOCAL_INIT
BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator
BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator
#endif
pos += 2;
}
@ -197,7 +212,7 @@ namespace System.Buffers
{
pos += Encoding.Unicode.GetBytes(value, span.Slice(pos));
#if NO_LOCAL_INIT
BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator
BinaryPrimitives.WriteUInt16BigEndian(span.Slice(pos, 2), 0); // Null terminator
#endif
pos += 2;
}

View file

@ -174,18 +174,16 @@ namespace Server
var tileList = new List<MultiTileEntry>();
// Skip the first 4 bytes
var reader = new BufferReader<byte>(data);
reader.Advance(4); // ???
reader.TryReadLittleEndian(out uint count);
var pos = 4;
var count = data.ReadUInt32LE(ref pos);
for (uint i = 0; i < count; i++)
{
reader.TryReadLittleEndian(out ushort itemid);
reader.TryReadLittleEndian(out short x);
reader.TryReadLittleEndian(out short y);
reader.TryReadLittleEndian(out short z);
reader.TryReadLittleEndian(out ushort flagValue);
var itemId = data.ReadUInt16LE(ref pos);
var x = data.ReadInt16LE(ref pos);
var y = data.ReadInt16LE(ref pos);
var z = data.ReadInt16LE(ref pos);
var flagValue = data.ReadUInt16LE(ref pos);
var tileFlag = flagValue switch
{
@ -194,10 +192,10 @@ namespace Server
_ => TileFlag.Background // 0
};
reader.TryReadLittleEndian(out uint clilocsCount);
reader.Advance(clilocsCount * 4); // bypass binary block
var clilocsCount = data.ReadUInt32LE(ref pos);
pos += (int)Math.Min(clilocsCount, int.MaxValue) * 4; // bypass binary block
tileList.Add(new MultiTileEntry(itemid, x, y, z, tileFlag));
tileList.Add(new MultiTileEntry(itemId, x, y, z, tileFlag));
}
Components[chunkID] = new MultiComponentList(tileList);