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);

View file

@ -580,30 +580,6 @@ consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License.
License notice for BufferReader/BufferWrite
---------------------------
The MIT License (MIT)
Copyright (c) Harry Pierson
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
License notice for csharp-argon-2
---------------------------
The MIT License (MIT)