diff --git a/Discord.Net.targets b/Discord.Net.targets
index 4ee3ada7e..94d2524d4 100644
--- a/Discord.Net.targets
+++ b/Discord.Net.targets
@@ -2,6 +2,7 @@
1.1.0-alpha
+ latest
RogueException
discord;discordapp
https://github.com/RogueException/Discord.Net
@@ -18,7 +19,7 @@
build-$(BuildNumber)
- $(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET
+ $(DefineConstants);FILESYSTEM;DEFAULTUDPCLIENT;DEFAULTWEBSOCKET;MSBUFFER
$(DefineConstants);FORMATSTR;UNIXTIME;MSTRYBUFFER;UDPDISPOSE
diff --git a/src/Discord.Net.Core/Discord.Net.Core.csproj b/src/Discord.Net.Core/Discord.Net.Core.csproj
index bce577ddd..adbed2ca0 100644
--- a/src/Discord.Net.Core/Discord.Net.Core.csproj
+++ b/src/Discord.Net.Core/Discord.Net.Core.csproj
@@ -5,10 +5,16 @@
Discord
The core components for the Discord.Net library.
net45;netstandard1.1;netstandard1.3
+ true
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Discord.Net.Core/Net/Rest/IRestClient.cs b/src/Discord.Net.Core/Net/Rest/IRestClient.cs
index addfa9061..61e05d07b 100644
--- a/src/Discord.Net.Core/Net/Rest/IRestClient.cs
+++ b/src/Discord.Net.Core/Net/Rest/IRestClient.cs
@@ -1,3 +1,4 @@
+using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -10,7 +11,7 @@ namespace Discord.Net.Rest
void SetCancelToken(CancellationToken cancelToken);
Task SendAsync(string method, string endpoint, CancellationToken cancelToken, bool headerOnly = false, string reason = null);
- Task SendAsync(string method, string endpoint, string json, CancellationToken cancelToken, bool headerOnly = false, string reason = null);
+ Task SendAsync(string method, string endpoint, ReadOnlyBuffer jsonPayload, CancellationToken cancelToken, bool headerOnly = false, string reason = null);
Task SendAsync(string method, string endpoint, IReadOnlyDictionary multipartParams, CancellationToken cancelToken, bool headerOnly = false, string reason = null);
}
}
diff --git a/src/Discord.Net.Core/Net/Rest/RestResponse.cs b/src/Discord.Net.Core/Net/Rest/RestResponse.cs
index 412ff4dce..ee6be282e 100644
--- a/src/Discord.Net.Core/Net/Rest/RestResponse.cs
+++ b/src/Discord.Net.Core/Net/Rest/RestResponse.cs
@@ -1,5 +1,5 @@
-using System.Collections.Generic;
-using System.IO;
+using System;
+using System.Collections.Generic;
using System.Net;
namespace Discord.Net.Rest
@@ -8,13 +8,13 @@ namespace Discord.Net.Rest
{
public HttpStatusCode StatusCode { get; }
public Dictionary Headers { get; }
- public Stream Stream { get; }
+ public ReadOnlyBuffer Data { get; }
- public RestResponse(HttpStatusCode statusCode, Dictionary headers, Stream stream)
+ public RestResponse(HttpStatusCode statusCode, Dictionary headers, ReadOnlyBuffer data)
{
StatusCode = statusCode;
Headers = headers;
- Stream = stream;
+ Data = data;
}
}
}
diff --git a/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs b/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs
index 7eccaabf2..652ec292c 100644
--- a/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs
+++ b/src/Discord.Net.Core/Net/WebSockets/IWebSocketClient.cs
@@ -6,8 +6,7 @@ namespace Discord.Net.WebSockets
{
public interface IWebSocketClient
{
- event Func BinaryMessage;
- event Func TextMessage;
+ event Func, bool, Task> Message;
event Func Closed;
void SetHeader(string key, string value);
@@ -16,6 +15,6 @@ namespace Discord.Net.WebSockets
Task ConnectAsync(string host);
Task DisconnectAsync();
- Task SendAsync(byte[] data, int index, int count, bool isText);
+ Task SendAsync(ReadOnlyBuffer data, bool isText);
}
}
diff --git a/src/Discord.Net.Core/_corefxlab/BufferExtensions.cs b/src/Discord.Net.Core/_corefxlab/BufferExtensions.cs
new file mode 100644
index 000000000..f6484dbae
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/BufferExtensions.cs
@@ -0,0 +1,160 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Collections.Sequences;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers
+{
+ public static class ExperimentalBufferExtensions
+ {
+ public static ReadOnlySpan ToSpan(this T bufferSequence) where T : ISequence>
+ {
+ Position position = Position.First;
+ ReadOnlyBuffer buffer;
+ ResizableArray array = new ResizableArray(1024);
+ while (bufferSequence.TryGet(ref position, out buffer))
+ {
+ array.AddAll(buffer.Span);
+ }
+ array.Resize(array.Count);
+ return array.Items.Slice(0, array.Count);
+ }
+
+ ///
+ /// Creates a new slice over the portion of the target array segment.
+ ///
+ /// The target array segment.
+ ///
+ public static Span Slice(this ArraySegment arraySegment)
+ {
+ return new Span(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
+ }
+
+ ///
+ /// Creates a new slice over the portion of the target array.
+ ///
+ /// The target array.
+ ///
+ /// Thrown if the 'array' parameter is null.
+ ///
+ public static Span Slice(this T[] array)
+ {
+ return new Span(array);
+ }
+
+ ///
+ /// Creates a new slice over the portion of the target array beginning
+ /// at 'start' index.
+ ///
+ /// The target array.
+ /// The index at which to begin the slice.
+ ///
+ /// Thrown if the 'array' parameter is null.
+ ///
+ ///
+ /// Thrown when the specified start index is not in range (<0 or >&eq;length).
+ ///
+ public static Span Slice(this T[] array, int start)
+ {
+ return new Span(array, start);
+ }
+
+ ///
+ /// Creates a new slice over the portion of the target array beginning
+ /// at 'start' index and with 'length' items.
+ ///
+ /// The target array.
+ /// The index at which to begin the slice.
+ /// The number of items in the new slice.
+ ///
+ /// Thrown if the 'array' parameter is null.
+ ///
+ ///
+ /// Thrown when the specified start or end index is not in range (<0 or >&eq;length).
+ ///
+ public static Span Slice(this T[] array, int start, int length)
+ {
+ return new Span(array, start, length);
+ }
+
+ ///
+ /// Creates a new readonly span over the portion of the target string.
+ ///
+ /// The target string.
+ /// Thrown when is null.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe ReadOnlySpan Slice(this string text)
+ {
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
+
+ int textLength = text.Length;
+
+ if (textLength == 0) return ReadOnlySpan.Empty;
+
+ fixed (char* charPointer = text)
+ {
+ return ReadOnlySpan.DangerousCreate(text, ref *charPointer, textLength);
+ }
+ }
+
+ ///
+ /// Creates a new readonly span over the portion of the target string, beginning at 'start'.
+ ///
+ /// The target string.
+ /// The index at which to begin this slice.
+ /// Thrown when is null.
+ ///
+ /// Thrown when the specified index is not in range (<0 or >Length).
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe ReadOnlySpan Slice(this string text, int start)
+ {
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
+
+ int textLength = text.Length;
+
+ if ((uint) start > (uint) textLength)
+ throw new ArgumentOutOfRangeException(nameof(start));
+
+ if (textLength - start == 0) return ReadOnlySpan.Empty;
+
+ fixed (char* charPointer = text)
+ {
+ return ReadOnlySpan.DangerousCreate(text, ref *(charPointer + start), textLength - start);
+ }
+ }
+
+ ///
+ /// Creates a new readonly span over the portion of the target string, beginning at , of given .
+ ///
+ /// The target string.
+ /// The index at which to begin this slice.
+ /// The number of items in the span.
+ /// Thrown when is null.
+ ///
+ /// Thrown when the specified or end index is not in range (<0 or >=Length).
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe ReadOnlySpan Slice(this string text, int start, int length)
+ {
+ if (text == null)
+ throw new ArgumentNullException(nameof(text));
+
+ int textLength = text.Length;
+
+ if ((uint)start > (uint)textLength || (uint)length > (uint)(textLength - start))
+ throw new ArgumentOutOfRangeException(nameof(start));
+
+ if (length == 0) return ReadOnlySpan.Empty;
+
+ fixed (char* charPointer = text)
+ {
+ return ReadOnlySpan.DangerousCreate(text, ref *(charPointer + start), length);
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferReader.cs b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferReader.cs
new file mode 100644
index 000000000..b0ce4e2ae
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferReader.cs
@@ -0,0 +1,109 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime;
+using System.Runtime.CompilerServices;
+
+namespace System.Binary
+{
+ ///
+ /// Reads bytes as primitives with specific endianness
+ ///
+ ///
+ /// For native formats, SpanExtensions.Read should be used.
+ /// Use these helpers when you need to read specific endinanness.
+ ///
+ public static class BufferReader
+ {
+ ///
+ /// Reads a structure of type out of a span of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadBigEndian<[Primitive]T>(this ReadOnlySpan span) where T : struct
+ => BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(span.Read()) : span.Read();
+
+ ///
+ /// Reads a structure of type out of a span of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadLittleEndian<[Primitive]T>(this ReadOnlySpan span) where T : struct
+ => BitConverter.IsLittleEndian ? span.Read() : UnsafeUtilities.Reverse(span.Read());
+
+ ///
+ /// Reads a structure of type out of a span of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadBigEndian<[Primitive]T>(this Span span) where T : struct
+ => BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(span.Read()) : span.Read();
+
+ ///
+ /// Reads a structure of type out of a span of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T ReadLittleEndian<[Primitive]T>(this Span span) where T : struct
+ => BitConverter.IsLittleEndian ? span.Read() : UnsafeUtilities.Reverse(span.Read());
+
+ ///
+ /// Reads a structure of type T out of a slice of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Read<[Primitive]T>(this Span slice)
+ where T : struct
+ {
+ RequiresInInclusiveRange(Unsafe.SizeOf(), (uint)slice.Length);
+ return Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference());
+ }
+
+ ///
+ /// Reads a structure of type T out of a slice of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static T Read<[Primitive]T>(this ReadOnlySpan slice)
+ where T : struct
+ {
+ RequiresInInclusiveRange(Unsafe.SizeOf(), (uint)slice.Length);
+ return Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference());
+ }
+
+ ///
+ /// Reads a structure of type T out of a slice of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryRead<[Primitive]T>(this ReadOnlySpan slice, out T value)
+ where T : struct
+ {
+ if (Unsafe.SizeOf() > (uint)slice.Length)
+ {
+ value = default;
+ return false;
+ }
+ value = Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference());
+ return true;
+ }
+
+ ///
+ /// Reads a structure of type T out of a slice of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryRead<[Primitive]T>(this Span slice, out T value)
+ where T : struct
+ {
+ if (Unsafe.SizeOf() > (uint)slice.Length)
+ {
+ value = default;
+ return false;
+ }
+ value = Unsafe.ReadUnaligned(ref slice.DangerousGetPinnableReference());
+ return true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresInInclusiveRange(int start, uint length)
+ {
+ if ((uint)start > length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferWriter.cs b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferWriter.cs
new file mode 100644
index 000000000..1deee40fe
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/BufferWriter.cs
@@ -0,0 +1,62 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime;
+using System.Runtime.CompilerServices;
+
+namespace System.Binary
+{
+ ///
+ /// Writes endian-specific primitives into spans.
+ ///
+ ///
+ /// Use these helpers when you need to write specific endinaness.
+ ///
+ public static class BufferWriter
+ {
+ ///
+ /// Writes a structure of type T to a span of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteBigEndian<[Primitive]T>(this Span span, T value) where T : struct
+ => span.Write(BitConverter.IsLittleEndian ? UnsafeUtilities.Reverse(value) : value);
+
+ ///
+ /// Writes a structure of type T to a span of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void WriteLittleEndian<[Primitive]T>(this Span span, T value) where T : struct
+ => span.Write(BitConverter.IsLittleEndian ? value : UnsafeUtilities.Reverse(value));
+
+
+
+ ///
+ /// Writes a structure of type T into a slice of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Write<[Primitive]T>(this Span slice, T value)
+ where T : struct
+ {
+ if ((uint)Unsafe.SizeOf() > (uint)slice.Length)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+ Unsafe.WriteUnaligned(ref slice.DangerousGetPinnableReference(), value);
+ }
+
+ ///
+ /// Writes a structure of type T into a slice of bytes.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool TryWrite<[Primitive]T>(this Span slice, T value)
+ where T : struct
+ {
+ if (Unsafe.SizeOf() > (uint)slice.Length)
+ {
+ return false;
+ }
+ Unsafe.WriteUnaligned(ref slice.DangerousGetPinnableReference(), value);
+ return true;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/UnsafeUtilities.cs b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/UnsafeUtilities.cs
new file mode 100644
index 000000000..f3a60c2b7
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Binary/System/Binary/UnsafeUtilities.cs
@@ -0,0 +1,68 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime
+{
+ ///
+ /// A collection of unsafe helper methods that we cannot implement in C#.
+ /// NOTE: these can be used for VeryBadThings(tm), so tread with care...
+ ///
+ internal static class UnsafeUtilities
+ {
+ ///
+ /// Reverses a primitive value - performs an endianness swap
+ ///
+ public static unsafe T Reverse<[Primitive]T>(T value) where T : struct
+ {
+ // note: relying on JIT goodness here!
+ if (typeof(T) == typeof(byte) || typeof(T) == typeof(sbyte)) {
+ return value;
+ }
+ else if (typeof(T) == typeof(ushort) || typeof(T) == typeof(short)) {
+ ushort val = 0;
+ Unsafe.Write(&val, value);
+ val = (ushort)((val >> 8) | (val << 8));
+ return Unsafe.Read(&val);
+ }
+ else if (typeof(T) == typeof(uint) || typeof(T) == typeof(int)
+ || typeof(T) == typeof(float)) {
+ uint val = 0;
+ Unsafe.Write(&val, value);
+ val = (val << 24)
+ | ((val & 0xFF00) << 8)
+ | ((val & 0xFF0000) >> 8)
+ | (val >> 24);
+ return Unsafe.Read(&val);
+ }
+ else if (typeof(T) == typeof(ulong) || typeof(T) == typeof(long)
+ || typeof(T) == typeof(double)) {
+ ulong val = 0;
+ Unsafe.Write(&val, value);
+ val = (val << 56)
+ | ((val & 0xFF00) << 40)
+ | ((val & 0xFF0000) << 24)
+ | ((val & 0xFF000000) << 8)
+ | ((val & 0xFF00000000) >> 8)
+ | ((val & 0xFF0000000000) >> 24)
+ | ((val & 0xFF000000000000) >> 40)
+ | (val >> 56);
+ return Unsafe.Read(&val);
+ }
+ else {
+ // default implementation
+ int len = Unsafe.SizeOf();
+ var val = stackalloc byte[len];
+ Unsafe.Write(val, value);
+ int to = len >> 1, dest = len - 1;
+ for (int i = 0; i < to; i++) {
+ var tmp = val[i];
+ val[i] = val[dest];
+ val[dest--] = tmp;
+ }
+ return Unsafe.Read(val);
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Buffer.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Buffer.cs
new file mode 100644
index 000000000..6fb6eabc5
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Buffer.cs
@@ -0,0 +1,238 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ [DebuggerTypeProxy(typeof(BufferDebuggerView<>))]
+ public struct Buffer
+ {
+ // The highest order bit of _index is used to discern whether _arrayOrOwnedBuffer is an array or an owned buffer
+ // if (_index >> 31) == 1, object _arrayOrOwnedBuffer is an OwnedBuffer
+ // else, object _arrayOrOwnedBuffer is a T[]
+ readonly object _arrayOrOwnedBuffer;
+ readonly int _index;
+ readonly int _length;
+
+ private const int bitMask = 0x7FFFFFFF;
+
+ internal Buffer(OwnedBuffer owner, int index, int length)
+ {
+ _arrayOrOwnedBuffer = owner;
+ _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with bitMask
+ _length = length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Buffer(T[] array)
+ {
+ if (array == null)
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ BufferPrimitivesThrowHelper.ThrowArrayTypeMismatchException(typeof(T));
+
+ _arrayOrOwnedBuffer = array;
+ _index = 0;
+ _length = array.Length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Buffer(T[] array, int start)
+ {
+ if (array == null)
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ BufferPrimitivesThrowHelper.ThrowArrayTypeMismatchException(typeof(T));
+
+ int arrayLength = array.Length;
+ if ((uint)start > (uint)arrayLength)
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ _arrayOrOwnedBuffer = array;
+ _index = start;
+ _length = arrayLength - start;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Buffer(T[] array, int start, int length)
+ {
+ if (array == null)
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if (default(T) == null && array.GetType() != typeof(T[]))
+ BufferPrimitivesThrowHelper.ThrowArrayTypeMismatchException(typeof(T));
+ if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ _arrayOrOwnedBuffer = array;
+ _index = start;
+ _length = length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlyBuffer(Buffer buffer)
+ {
+ // There is no need to 'and' _index by the bit mask here
+ // since the constructor will set the highest order bit again anyway
+ if (buffer._index < 0)
+ return new ReadOnlyBuffer(Unsafe.As>(buffer._arrayOrOwnedBuffer), buffer._index, buffer._length);
+ return new ReadOnlyBuffer(Unsafe.As(buffer._arrayOrOwnedBuffer), buffer._index, buffer._length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Buffer(T[] array)
+ {
+ return new Buffer(array, 0, array.Length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator Buffer(ArraySegment arraySegment)
+ {
+ return new Buffer(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
+ }
+
+ public static Buffer Empty { get; } = OwnedBuffer.EmptyArray;
+
+ public int Length => _length;
+
+ public bool IsEmpty => Length == 0;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Buffer Slice(int start)
+ {
+ if ((uint)start > (uint)_length)
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ // There is no need to and _index by the bit mask here
+ // since the constructor will set the highest order bit again anyway
+ if (_index < 0)
+ return new Buffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, _length - start);
+ return new Buffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, _length - start);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Buffer Slice(int start, int length)
+ {
+ if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ // There is no need to 'and' _index by the bit mask here
+ // since the constructor will set the highest order bit again anyway
+ if (_index < 0)
+ return new Buffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, length);
+ return new Buffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, length);
+ }
+
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ if (_index < 0)
+ return Unsafe.As>(_arrayOrOwnedBuffer).AsSpan(_index & bitMask, _length);
+ return new Span(Unsafe.As(_arrayOrOwnedBuffer), _index, _length);
+ }
+ }
+
+ public BufferHandle Retain(bool pin = false)
+ {
+ BufferHandle bufferHandle;
+ if (pin)
+ {
+ if (_index < 0)
+ {
+ bufferHandle = Unsafe.As>(_arrayOrOwnedBuffer).Pin(_index & bitMask);
+ }
+ else
+ {
+ var handle = GCHandle.Alloc(Unsafe.As(_arrayOrOwnedBuffer), GCHandleType.Pinned);
+ unsafe
+ {
+ var pointer = OwnedBuffer.Add((void*)handle.AddrOfPinnedObject(), _index);
+ bufferHandle = new BufferHandle(null, pointer, handle);
+ }
+ }
+ }
+ else
+ {
+ if (_index < 0)
+ {
+ Unsafe.As>(_arrayOrOwnedBuffer).Retain();
+ bufferHandle = new BufferHandle(Unsafe.As>(_arrayOrOwnedBuffer));
+ }
+ else
+ {
+ bufferHandle = new BufferHandle(null);
+ }
+ }
+ return bufferHandle;
+ }
+
+ public bool TryGetArray(out ArraySegment arraySegment)
+ {
+ if (_index < 0)
+ {
+ if (Unsafe.As>(_arrayOrOwnedBuffer).TryGetArray(out var segment))
+ {
+ arraySegment = new ArraySegment(segment.Array, segment.Offset + (_index & bitMask), _length);
+ return true;
+ }
+ }
+ else
+ {
+ arraySegment = new ArraySegment(Unsafe.As(_arrayOrOwnedBuffer), _index, _length);
+ return true;
+ }
+
+ arraySegment = default;
+ return false;
+ }
+
+ public T[] ToArray() => Span.ToArray();
+
+ public void CopyTo(Span span) => Span.CopyTo(span);
+
+ public void CopyTo(Buffer buffer) => Span.CopyTo(buffer.Span);
+
+ public bool TryCopyTo(Span span) => Span.TryCopyTo(span);
+
+ public bool TryCopyTo(Buffer buffer) => Span.TryCopyTo(buffer.Span);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj)
+ {
+ if (obj is ReadOnlyBuffer readOnlyBuffer)
+ {
+ return readOnlyBuffer.Equals(this);
+ }
+ else if (obj is Buffer buffer)
+ {
+ return Equals(buffer);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public bool Equals(Buffer other)
+ {
+ return
+ _arrayOrOwnedBuffer == other._arrayOrOwnedBuffer &&
+ (_index & bitMask) == (other._index & bitMask) &&
+ _length == other._length;
+ }
+
+ [EditorBrowsable( EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ return HashingHelper.CombineHashCodes(_arrayOrOwnedBuffer.GetHashCode(), (_index & bitMask).GetHashCode(), _length.GetHashCode());
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferExtensions.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferExtensions.cs
new file mode 100644
index 000000000..fbb8a3e67
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferExtensions.cs
@@ -0,0 +1,19 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+namespace System.Buffers
+{
+ public static class BufferExtensions
+ {
+ public static bool SequenceEqual(this Buffer first, Buffer second) where T : struct, IEquatable
+ {
+ return first.Span.SequenceEqual(second.Span);
+ }
+
+ public static bool SequenceEqual(this ReadOnlyBuffer first, ReadOnlyBuffer second) where T : struct, IEquatable
+ {
+ return first.Span.SequenceEqual(second.Span);
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferHandle.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferHandle.cs
new file mode 100644
index 000000000..9a15a6bb9
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferHandle.cs
@@ -0,0 +1,46 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime;
+using System.Runtime.InteropServices;
+
+namespace System.Buffers
+{
+ public unsafe struct BufferHandle : IDisposable
+ {
+ IRetainable _owner;
+ void* _pointer;
+ GCHandle _handle;
+
+ public BufferHandle(IRetainable owner, void* pinnedPointer, GCHandle handle = default)
+ {
+ _pointer = pinnedPointer;
+ _handle = handle;
+ _owner = owner;
+ }
+
+ public BufferHandle(IRetainable owner) : this(owner, null) { }
+
+ public void* PinnedPointer {
+ get {
+ if (_pointer == null) BufferPrimitivesThrowHelper.ThrowInvalidOperationException();
+ return _pointer;
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_handle.IsAllocated) {
+ _handle.Free();
+ }
+
+ if (_owner != null) {
+ _owner.Release();
+ _owner = null;
+ }
+
+ _pointer = null;
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferPool.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferPool.cs
new file mode 100644
index 000000000..3520e7145
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/BufferPool.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Buffers
+{
+ public abstract class BufferPool : IDisposable
+ {
+ public static BufferPool Default => Internal.ManagedBufferPool.Shared;
+
+ public abstract OwnedBuffer Rent(int minimumBufferSize);
+
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~BufferPool()
+ {
+ Dispose(false);
+ }
+
+ protected abstract void Dispose(bool disposing);
+ }
+}
\ No newline at end of file
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IOutput.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IOutput.cs
new file mode 100644
index 000000000..948b8b7e2
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IOutput.cs
@@ -0,0 +1,14 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Buffers
+{
+ public interface IOutput
+ {
+ Span Buffer { get; }
+ void Advance(int bytes);
+
+ /// desiredBufferLength == 0 means "i don't care"
+ void Enlarge(int desiredBufferLength = 0);
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IRetainable.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IRetainable.cs
new file mode 100644
index 000000000..a39481239
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/IRetainable.cs
@@ -0,0 +1,12 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Buffers
+{
+ public interface IRetainable
+ {
+ void Retain();
+ void Release();
+ bool IsRetained { get; }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/OwnedBuffer.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/OwnedBuffer.cs
new file mode 100644
index 000000000..4fbf9a58b
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/OwnedBuffer.cs
@@ -0,0 +1,75 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Runtime;
+using System.Runtime.CompilerServices;
+
+namespace System.Buffers
+{
+ public abstract class OwnedBuffer : IDisposable, IRetainable
+ {
+ protected OwnedBuffer() { }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator OwnedBuffer(T[] array)
+ {
+ return new Internal.OwnedArray(array);
+ }
+
+ public abstract int Length { get; }
+
+ public abstract Span AsSpan(int index, int length);
+
+ public virtual Span AsSpan()
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer));
+ return AsSpan(0, Length);
+ }
+
+ public Buffer Buffer
+ {
+ get {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer));
+ return new Buffer(this, 0, Length);
+ }
+ }
+
+ public ReadOnlyBuffer ReadOnlyBuffer
+ {
+ get {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedBuffer));
+ return new ReadOnlyBuffer(this, 0, Length);
+ }
+ }
+
+ public abstract BufferHandle Pin(int index = 0);
+
+ protected internal abstract bool TryGetArray(out ArraySegment arraySegment);
+
+ #region Lifetime Management
+ public abstract bool IsDisposed { get; }
+
+ public void Dispose()
+ {
+ if (IsRetained) throw new InvalidOperationException("outstanding references detected.");
+ Dispose(true);
+ }
+
+ protected abstract void Dispose(bool disposing);
+
+ public abstract bool IsRetained { get; }
+
+ public abstract void Retain();
+
+ public abstract void Release();
+ #endregion
+
+ protected internal static unsafe void* Add(void* pointer, int offset)
+ {
+ return (byte*)pointer + ((ulong)Unsafe.SizeOf() * (ulong)offset);
+ }
+
+ internal static readonly T[] EmptyArray = new T[0];
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/ReadOnlyBuffer.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/ReadOnlyBuffer.cs
new file mode 100644
index 000000000..2158496c5
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/ReadOnlyBuffer.cs
@@ -0,0 +1,223 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.ComponentModel;
+using System.Diagnostics;
+using System.Runtime;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace System
+{
+ [DebuggerTypeProxy(typeof(ReadOnlyBufferDebuggerView<>))]
+ public struct ReadOnlyBuffer
+ {
+ // The highest order bit of _index is used to discern whether _arrayOrOwnedBuffer is an array or an owned buffer
+ // if (_index >> 31) == 1, object _arrayOrOwnedBuffer is an OwnedBuffer
+ // else, object _arrayOrOwnedBuffer is a T[]
+ readonly object _arrayOrOwnedBuffer;
+ readonly int _index;
+ readonly int _length;
+
+ private const int bitMask = 0x7FFFFFFF;
+
+ internal ReadOnlyBuffer(OwnedBuffer owner, int index, int length)
+ {
+ _arrayOrOwnedBuffer = owner;
+ _index = index | (1 << 31); // Before using _index, check if _index < 0, then 'and' it with bitMask
+ _length = length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyBuffer(T[] array)
+ {
+ if (array == null)
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+ _arrayOrOwnedBuffer = array;
+ _index = 0;
+ _length = array.Length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyBuffer(T[] array, int start)
+ {
+ if (array == null)
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+
+ int arrayLength = array.Length;
+ if ((uint)start > (uint)arrayLength)
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ _arrayOrOwnedBuffer = array;
+ _index = start;
+ _length = arrayLength - start;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyBuffer(T[] array, int start, int length)
+ {
+ if (array == null)
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(ExceptionArgument.array);
+ if ((uint)start > (uint)array.Length || (uint)length > (uint)(array.Length - start))
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ _arrayOrOwnedBuffer = array;
+ _index = start;
+ _length = length;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlyBuffer(T[] array)
+ {
+ return new ReadOnlyBuffer(array, 0, array.Length);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static implicit operator ReadOnlyBuffer(ArraySegment arraySegment)
+ {
+ return new ReadOnlyBuffer(arraySegment.Array, arraySegment.Offset, arraySegment.Count);
+ }
+
+ public static ReadOnlyBuffer Empty { get; } = OwnedBuffer.EmptyArray;
+
+ public int Length => _length;
+
+ public bool IsEmpty => Length == 0;
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyBuffer Slice(int start)
+ {
+ if ((uint)start > (uint)_length)
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ // There is no need to 'and' _index by the bit mask here
+ // since the constructor will set the highest order bit again anyway
+ if (_index < 0)
+ return new ReadOnlyBuffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, _length - start);
+ return new ReadOnlyBuffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, _length - start);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ReadOnlyBuffer Slice(int start, int length)
+ {
+ if ((uint)start > (uint)_length || (uint)length > (uint)(_length - start))
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException(ExceptionArgument.start);
+
+ // There is no need to 'and' _index by the bit mask here
+ // since the constructor will set the highest order bit again anyway
+ if (_index < 0)
+ return new ReadOnlyBuffer(Unsafe.As>(_arrayOrOwnedBuffer), _index + start, length);
+ return new ReadOnlyBuffer(Unsafe.As(_arrayOrOwnedBuffer), _index + start, length);
+ }
+
+ public ReadOnlySpan Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ if (_index < 0)
+ return Unsafe.As>(_arrayOrOwnedBuffer).AsSpan(_index & bitMask, _length);
+ return new ReadOnlySpan(Unsafe.As(_arrayOrOwnedBuffer), _index, _length);
+ }
+ }
+
+ public BufferHandle Retain(bool pin = false)
+ {
+ BufferHandle bufferHandle;
+ if (pin)
+ {
+ if (_index < 0)
+ {
+ bufferHandle = Unsafe.As>(_arrayOrOwnedBuffer).Pin(_index & bitMask);
+ }
+ else
+ {
+ var handle = GCHandle.Alloc(Unsafe.As(_arrayOrOwnedBuffer), GCHandleType.Pinned);
+ unsafe
+ {
+ var pointer = OwnedBuffer.Add((void*)handle.AddrOfPinnedObject(), _index);
+ bufferHandle = new BufferHandle(null, pointer, handle);
+ }
+ }
+ }
+ else
+ {
+ if (_index < 0)
+ {
+ Unsafe.As>(_arrayOrOwnedBuffer).Retain();
+ bufferHandle = new BufferHandle(Unsafe.As>(_arrayOrOwnedBuffer));
+ }
+ else
+ {
+ bufferHandle = new BufferHandle(null);
+ }
+ }
+ return bufferHandle;
+ }
+
+ public T[] ToArray() => Span.ToArray();
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public bool DangerousTryGetArray(out ArraySegment arraySegment)
+ {
+ if (_index < 0)
+ {
+ if (Unsafe.As>(_arrayOrOwnedBuffer).TryGetArray(out var segment))
+ {
+ arraySegment = new ArraySegment(segment.Array, segment.Offset + (_index & bitMask), _length);
+ return true;
+ }
+ }
+ else
+ {
+ arraySegment = new ArraySegment(Unsafe.As(_arrayOrOwnedBuffer), _index, _length);
+ return true;
+ }
+
+ arraySegment = default;
+ return false;
+ }
+
+ public void CopyTo(Span span) => Span.CopyTo(span);
+
+ public void CopyTo(Buffer buffer) => Span.CopyTo(buffer.Span);
+
+ public bool TryCopyTo(Span span) => Span.TryCopyTo(span);
+
+ public bool TryCopyTo(Buffer buffer) => Span.TryCopyTo(buffer.Span);
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override bool Equals(object obj)
+ {
+ if (obj is ReadOnlyBuffer readOnlyBuffer)
+ {
+ return Equals(readOnlyBuffer);
+ }
+ else if (obj is Buffer buffer)
+ {
+ return Equals(buffer);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public bool Equals(ReadOnlyBuffer other)
+ {
+ return
+ _arrayOrOwnedBuffer == other._arrayOrOwnedBuffer &&
+ (_index & bitMask) == (other._index & bitMask) &&
+ _length == other._length;
+ }
+
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public override int GetHashCode()
+ {
+ return HashingHelper.CombineHashCodes(_arrayOrOwnedBuffer.GetHashCode(), (_index & bitMask).GetHashCode(), _length.GetHashCode());
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Transformation.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Transformation.cs
new file mode 100644
index 000000000..d27d1acc9
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Buffers/Transformation.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Buffers
+{
+ public abstract class Transformation
+ {
+ public abstract TransformationStatus Transform(ReadOnlySpan source, Span destination, out int bytesConsumed, out int bytesWritten);
+ }
+
+ public enum TransformationStatus
+ {
+ Done,
+ DestinationTooSmall,
+ NeedMoreSourceData,
+ InvalidData // TODO: how do we communicate details of the error
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/ManagedBufferPool.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/ManagedBufferPool.cs
new file mode 100644
index 000000000..f2ef069e5
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/ManagedBufferPool.cs
@@ -0,0 +1,101 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.Buffers.Internal
+{
+ internal sealed class ManagedBufferPool : BufferPool
+ {
+ readonly static ManagedBufferPool s_shared = new ManagedBufferPool();
+
+ public static ManagedBufferPool Shared
+ {
+ get
+ {
+ return s_shared;
+ }
+ }
+
+ public override OwnedBuffer Rent(int minimumBufferSize)
+ {
+ var buffer = new ArrayPoolBuffer(minimumBufferSize);
+ return buffer;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ }
+
+ private sealed class ArrayPoolBuffer : OwnedBuffer
+ {
+ byte[] _array;
+ bool _disposed;
+ int _referenceCount;
+
+ public ArrayPoolBuffer(int size)
+ {
+ _array = ArrayPool.Shared.Rent(size);
+ }
+
+ public override int Length => _array.Length;
+
+ public override bool IsDisposed => _disposed;
+
+ public override bool IsRetained => _referenceCount > 0;
+
+ public override Span AsSpan(int index, int length)
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ArrayPoolBuffer));
+ return new Span(_array, index, length);
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ var array = Interlocked.Exchange(ref _array, null);
+ if (array != null) {
+ _disposed = true;
+ ArrayPool.Shared.Return(array);
+ }
+ }
+
+ protected internal override bool TryGetArray(out ArraySegment arraySegment)
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ManagedBufferPool));
+ arraySegment = new ArraySegment(_array);
+ return true;
+ }
+
+ public override BufferHandle Pin(int index = 0)
+ {
+ unsafe
+ {
+ Retain(); // this checks IsDisposed
+ var handle = GCHandle.Alloc(_array, GCHandleType.Pinned);
+ var pointer = Add((void*)handle.AddrOfPinnedObject(), index);
+ return new BufferHandle(this, pointer, handle);
+ }
+ }
+
+ public override void Retain()
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(ArrayPoolBuffer));
+ Interlocked.Increment(ref _referenceCount);
+ }
+
+ public override void Release()
+ {
+ var newRefCount = Interlocked.Decrement(ref _referenceCount);
+ if (newRefCount == 0) {
+ Dispose();
+ return;
+ }
+ if(newRefCount < 0) {
+ throw new InvalidOperationException();
+ }
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/OwnedArray.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/OwnedArray.cs
new file mode 100644
index 000000000..4e782f530
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Internal/OwnedArray.cs
@@ -0,0 +1,88 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace System.Buffers.Internal
+{
+ internal class OwnedArray : OwnedBuffer
+ {
+ T[] _array;
+ int _referenceCount;
+
+ public OwnedArray(int length)
+ {
+ _array = new T[length];
+ }
+
+ public OwnedArray(T[] array)
+ {
+ if (array == null) BufferPrimitivesThrowHelper.ThrowArgumentNullException(nameof(array));
+ _array = array;
+ }
+
+ public static implicit operator OwnedArray(T[] array) => new OwnedArray(array);
+
+ public override int Length => _array.Length;
+
+ public override Span AsSpan(int index, int length)
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray));
+ return new Span(_array, index, length);
+ }
+
+ public override Span AsSpan()
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray));
+ return new Span(_array, 0, _array.Length);
+ }
+
+ public override BufferHandle Pin(int index = 0)
+ {
+ unsafe
+ {
+ Retain();
+ var handle = GCHandle.Alloc(_array, GCHandleType.Pinned);
+ var pointer = Add((void*)handle.AddrOfPinnedObject(), index);
+ return new BufferHandle(this, pointer, handle);
+ }
+ }
+
+ protected internal override bool TryGetArray(out ArraySegment arraySegment)
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray));
+ arraySegment = new ArraySegment(_array);
+ return true;
+ }
+
+ protected override void Dispose(bool disposing)
+ {
+ _array = null;
+ }
+
+ public override void Retain()
+ {
+ if (IsDisposed) BufferPrimitivesThrowHelper.ThrowObjectDisposedException(nameof(OwnedArray));
+ Interlocked.Increment(ref _referenceCount);
+ }
+
+ public override void Release()
+ {
+ if (!IsRetained) BufferPrimitivesThrowHelper.ThrowInvalidOperationException();
+ if (Interlocked.Decrement(ref _referenceCount) == 0)
+ {
+ OnNoReferences();
+ }
+ }
+
+ protected virtual void OnNoReferences()
+ {
+ }
+
+ public override bool IsRetained => _referenceCount > 0;
+
+ public override bool IsDisposed => _array == null;
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferDebuggerView.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferDebuggerView.cs
new file mode 100644
index 000000000..323f36fa2
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferDebuggerView.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+
+namespace System.Runtime
+{
+ internal class BufferDebuggerView
+ {
+ private ReadOnlyBuffer _buffer;
+
+ public BufferDebuggerView(Buffer buffer)
+ {
+ _buffer = buffer;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items
+ {
+ get {
+ return _buffer.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferPrimitivesThrowHelper.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferPrimitivesThrowHelper.cs
new file mode 100644
index 000000000..881dc2f2a
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/BufferPrimitivesThrowHelper.cs
@@ -0,0 +1,131 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime
+{
+ internal static class BufferPrimitivesThrowHelper
+ {
+ public static void ThrowArgumentNullException(string argument)
+ {
+ throw new ArgumentNullException(argument);
+ }
+
+ public static void ThrowArgumentNullException(ExceptionArgument argument)
+ {
+ throw GetArgumentNullException(argument);
+ }
+
+ public static void ThrowArgumentException()
+ {
+ throw GetArgumentException();
+ }
+
+ public static void ThrowArgumentException(ExceptionArgument argument)
+ {
+ throw GetArgumentException(argument);
+ }
+
+ public static void ThrowArgumentOutOfRangeException()
+ {
+ throw GetArgumentOutOfRangeException();
+ }
+
+ public static void ThrowArgumentOutOfRangeException(ExceptionArgument argument)
+ {
+ throw GetArgumentOutOfRangeException(argument);
+ }
+
+ public static void ThrowInvalidOperationException()
+ {
+ throw GetInvalidOperationException();
+ }
+
+ public static void ThrowInvalidOperationException_ForBoxingSpans()
+ {
+ throw GetInvalidOperationException_ForBoxingSpans();
+ }
+
+ public static void ThrowObjectDisposedException(string objectName)
+ {
+ throw GetObjectDisposedException(objectName);
+ }
+
+ public static void ThrowArrayTypeMismatchException(Type type)
+ {
+ throw CreateArrayTypeMismatchException(type);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentNullException GetArgumentNullException(ExceptionArgument argument)
+ {
+ return new ArgumentNullException(GetArgumentName(argument));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentException GetArgumentException()
+ {
+ return new ArgumentException();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentException GetArgumentException(ExceptionArgument argument)
+ {
+ return new ArgumentException(GetArgumentName(argument));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentOutOfRangeException GetArgumentOutOfRangeException()
+ {
+ return new ArgumentOutOfRangeException();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArgumentOutOfRangeException GetArgumentOutOfRangeException(ExceptionArgument argument)
+ {
+ return new ArgumentOutOfRangeException(GetArgumentName(argument));
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static InvalidOperationException GetInvalidOperationException()
+ {
+ return new InvalidOperationException();
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static InvalidOperationException GetInvalidOperationException_ForBoxingSpans()
+ {
+ return new InvalidOperationException("Spans must not be boxed");
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ObjectDisposedException GetObjectDisposedException(string objectName)
+ {
+ return new ObjectDisposedException(objectName);
+ }
+
+ [MethodImpl(MethodImplOptions.NoInlining)]
+ private static ArrayTypeMismatchException CreateArrayTypeMismatchException(Type type)
+ {
+ return new ArrayTypeMismatchException(type.ToString());
+ }
+
+ private static string GetArgumentName(ExceptionArgument argument)
+ {
+ Debug.Assert(Enum.IsDefined(typeof(ExceptionArgument), argument),
+ "The enum value is not defined, please check the ExceptionArgument Enum.");
+
+ return argument.ToString();
+ }
+ }
+
+ internal enum ExceptionArgument
+ {
+ pointer,
+ array,
+ start
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/Contract.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/Contract.cs
new file mode 100644
index 000000000..b4982f718
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/Contract.cs
@@ -0,0 +1,122 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Runtime
+{
+ internal static class Contract
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void Requires(bool condition)
+ {
+ if (!condition)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresNotNull(ExceptionArgument argument, T obj) where T : class
+ {
+ if (obj == null)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(argument);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void RequiresNotNull(ExceptionArgument argument, void* ptr)
+ {
+ if (ptr == null)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentNullException(argument);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static unsafe void RequiresSameReference(void* ptr0, void* ptr1)
+ {
+ if (ptr0 != ptr1)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentException(ExceptionArgument.pointer);
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresNonNegative(int n)
+ {
+ if (n < 0)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresInRange(int start, uint length)
+ {
+ if ((uint)start >= length)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresInRange(uint start, uint length)
+ {
+ if (start >= length)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresInInclusiveRange(int start, uint length)
+ {
+ if ((uint)start > length)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal static unsafe void RequiresOneNotNull(T[] array, void* pointer)
+ {
+ if (array == null && pointer == null)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresInInclusiveRange( uint start, uint length)
+ {
+ if (start > length)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresInInclusiveRange(int start, int length, uint existingLength)
+ {
+ if ((uint)start > existingLength
+ || length < 0
+ || (uint)(start + length) > existingLength)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static void RequiresInInclusiveRange(uint start, uint length, uint existingLength)
+ {
+ if (start > existingLength
+ || (start + length) > existingLength)
+ {
+ BufferPrimitivesThrowHelper.ThrowArgumentOutOfRangeException();
+ }
+ }
+ }
+}
+
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/HashingHelper.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/HashingHelper.cs
new file mode 100644
index 000000000..6f8eb5f93
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/HashingHelper.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Runtime
+{
+ static class HashingHelper
+ {
+ public static int CombineHashCodes(int left, int right)
+ {
+ return ((left << 5) + left) ^ right;
+ }
+
+ public static int CombineHashCodes(int h1, int h2, int h3)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2), h3);
+ }
+
+ public static int CombineHashCodes(int h1, int h2, int h3, int h4)
+ {
+ return CombineHashCodes(CombineHashCodes(h1, h2), CombineHashCodes(h3, h4));
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/PrimitiveAttribute.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/PrimitiveAttribute.cs
new file mode 100644
index 000000000..8ef158b77
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/PrimitiveAttribute.cs
@@ -0,0 +1,11 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Runtime
+{
+ [AttributeUsage(AttributeTargets.GenericParameter)]
+ public sealed class PrimitiveAttribute : Attribute
+ {
+ }
+}
+
diff --git a/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/ReadOnlyBufferDebuggerView.cs b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/ReadOnlyBufferDebuggerView.cs
new file mode 100644
index 000000000..4d3ff9912
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Buffers.Primitives/System/Runtime/ReadOnlyBufferDebuggerView.cs
@@ -0,0 +1,25 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Diagnostics;
+
+namespace System.Runtime
+{
+ internal class ReadOnlyBufferDebuggerView
+ {
+ private ReadOnlyBuffer _buffer;
+
+ public ReadOnlyBufferDebuggerView(ReadOnlyBuffer buffer)
+ {
+ _buffer = buffer;
+ }
+
+ [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)]
+ public T[] Items
+ {
+ get {
+ return _buffer.ToArray();
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ArrayList.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ArrayList.cs
new file mode 100644
index 000000000..209b89bda
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ArrayList.cs
@@ -0,0 +1,41 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+
+namespace System.Collections.Sequences
+{
+ // This type is illustrating how to implement the new enumerable on index based datastructure
+ public sealed class ArrayList : ISequence
+ {
+ ResizableArray _items;
+
+ public ArrayList()
+ {
+ _items = new ResizableArray(0);
+ }
+ public ArrayList(int capacity)
+ {
+ _items = new ResizableArray(capacity);
+ }
+
+ public int Length => _items.Count;
+
+ public T this[int index] => _items[index];
+
+ public void Add(T item)
+ {
+ _items.Add(item);
+ }
+
+ public SequenceEnumerator GetEnumerator()
+ {
+ return new SequenceEnumerator(this);
+ }
+
+ public bool TryGet(ref Position position, out T item, bool advance = true)
+ {
+ return _items.TryGet(ref position, out item, advance);
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ISequence.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ISequence.cs
new file mode 100644
index 000000000..f907fe364
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ISequence.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace System.Collections.Sequences
+{
+ // new interface
+ public interface ISequence
+ {
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ bool TryGet(ref Position position, out T item, bool advance = true);
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/Position.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/Position.cs
new file mode 100644
index 000000000..b4eda126b
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/Position.cs
@@ -0,0 +1,47 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Sequences
+{
+ public struct Position : IEquatable
+ {
+ public object ObjectPosition;
+ public int IntegerPosition;
+ public int Tag;
+
+ public static readonly Position First = new Position();
+ public static readonly Position AfterLast = new Position() { IntegerPosition = int.MaxValue - 1 };
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator==(Position left, Position right)
+ {
+ return left.Equals(right);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool operator!=(Position left, Position right)
+ {
+ return left.Equals(right);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool Equals(Position other)
+ {
+ return IntegerPosition == other.IntegerPosition && ObjectPosition == other.ObjectPosition;
+ }
+
+ public override int GetHashCode()
+ {
+ return ObjectPosition == null ? IntegerPosition.GetHashCode() : ObjectPosition.GetHashCode();
+ }
+
+ public override bool Equals(object obj)
+ {
+ if(obj is Position)
+ return base.Equals((Position)obj);
+ return false;
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ResizableArray.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ResizableArray.cs
new file mode 100644
index 000000000..d09ab2cdf
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/ResizableArray.cs
@@ -0,0 +1,124 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+
+namespace System.Collections.Sequences
+{
+ // a List like type designed to be embeded in other types
+ public struct ResizableArray
+ {
+ private T[] _array;
+ private int _count;
+
+ public ResizableArray(int capacity)
+ {
+ _array = new T[capacity];
+ _count = 0;
+ }
+
+ public ResizableArray(T[] array, int count = 0)
+ {
+ _array = array;
+ _count = count;
+ }
+
+ public T[] Items
+ {
+ get { return _array; }
+ set { _array = value; }
+ }
+ public int Count
+ {
+ get { return _count; }
+ set { _count = value; }
+ }
+
+ public int Capacity => _array.Length;
+
+ public T this[int index]
+ {
+ get {
+ if (index > _count - 1) throw new IndexOutOfRangeException();
+ return _array[index];
+ }
+ set {
+ if (index > _count - 1) throw new IndexOutOfRangeException();
+ _array[index] = value;
+ }
+ }
+
+ public void Add(T item)
+ {
+ if (_array.Length == _count) {
+ Resize();
+ }
+ _array[_count++] = item;
+ }
+
+ public void AddAll(T[] items)
+ {
+ if (items.Length > _array.Length - _count) {
+ Resize(items.Length + _count);
+ }
+ items.CopyTo(_array, _count);
+ _count += items.Length;
+ }
+
+ public void AddAll(ReadOnlySpan items)
+ {
+ if (items.Length > _array.Length - _count) {
+ Resize(items.Length + _count);
+ }
+ items.CopyTo(new Span(_array, _count));
+ _count += items.Length;
+ }
+
+ public void Clear()
+ {
+ _count = 0;
+ }
+
+ public T[] Resize(int newSize = -1)
+ {
+ var oldArray = _array;
+ if (newSize == -1) {
+ if(_array == null || _array.Length == 0) {
+ newSize = 4;
+ }
+ else {
+ newSize = _array.Length << 1;
+ }
+ }
+
+ var newArray = new T[newSize];
+ new Span(_array, 0, _count).CopyTo(newArray);
+ _array = newArray;
+ return oldArray;
+ }
+
+ public T[] Resize(T[] newArray)
+ {
+ if (newArray.Length < _count) throw new ArgumentOutOfRangeException(nameof(newArray));
+ var oldArray = _array;
+ Array.Copy(_array, 0, newArray, 0, _count);
+ _array = newArray;
+ return oldArray;
+ }
+
+ public bool TryGet(ref Position position, out T item, bool advance = true)
+ {
+ if (position.IntegerPosition < _count) {
+ item = _array[position.IntegerPosition];
+ if (advance) { position.IntegerPosition++; }
+ return true;
+ }
+
+ item = default;
+ position = Position.AfterLast;
+ return false;
+ }
+
+ public ArraySegment Full => new ArraySegment(_array, 0, _count);
+ public ArraySegment Free => new ArraySegment(_array, _count, _array.Length - _count);
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/SequenceEnumerator.cs b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/SequenceEnumerator.cs
new file mode 100644
index 000000000..08b26aa8b
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Collections.Sequences/System/Collections/Sequences/SequenceEnumerator.cs
@@ -0,0 +1,31 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+namespace System.Collections.Sequences
+{
+ public struct SequenceEnumerator
+ {
+ Position _position;
+ ISequence _sequence;
+ T _current;
+ bool first; // this is needed so that MoveNext does not advance the first time it's called
+
+ public SequenceEnumerator(ISequence sequence) {
+ _sequence = sequence;
+ _position = Position.First;
+ _current = default;
+ first = true;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool MoveNext() {
+ var result = _sequence.TryGet(ref _position, out _current, advance: !first);
+ first = false;
+ return result;
+ }
+
+ public T Current => _current;
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/CompositeFormat.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/CompositeFormat.cs
new file mode 100644
index 000000000..5a7f14e1c
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/CompositeFormat.cs
@@ -0,0 +1,402 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Buffers;
+using System.Diagnostics;
+
+namespace System.Text.Formatting
+{
+
+ // This whole API is very speculative, i.e. I am not sure I am happy with the design
+ // This API is trying to do composite formatting without boxing (or any other allocations).
+ // And because not all types in the platfrom implement IBufferFormattable (in particular built-in primitives don't),
+ // it needs to play some tricks with generic type parameters. But as you can see at the end of AppendUntyped, I am not sure how to tick the type system
+ // not never box.
+ public static class CompositeFormattingExtensions
+ {
+ public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0) where TFormatter : ITextOutput
+ {
+ var reader = new CompositeFormatReader(compositeFormat);
+ while (true)
+ {
+ var segment = reader.Next();
+ if (segment == null) return;
+
+ if (segment.Value.Count == 0) // insertion point
+ {
+ if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format);
+ else throw new Exception("invalid insertion point");
+ }
+ else // literal
+ {
+ formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count);
+ }
+ }
+ }
+
+ public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1) where TFormatter : ITextOutput
+ {
+ var reader = new CompositeFormatReader(compositeFormat);
+ while (true)
+ {
+ var segment = reader.Next();
+ if (segment == null) return;
+
+ if (segment.Value.Count == 0) // insertion point
+ {
+ if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format);
+ else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format);
+ else throw new Exception("invalid insertion point");
+ }
+ else // literal
+ {
+ formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count);
+ }
+ }
+ }
+
+ public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1, T2 arg2) where TFormatter : ITextOutput
+ {
+ var reader = new CompositeFormatReader(compositeFormat);
+ while (true)
+ {
+ var segment = reader.Next();
+ if (segment == null) return;
+
+ if (segment.Value.Count == 0) // insertion point
+ {
+ if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format);
+ else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format);
+ else if (segment.Value.Index == 2) formatter.AppendUntyped(arg2, segment.Value.Format);
+ else throw new Exception("invalid insertion point");
+ }
+ else // literal
+ {
+ formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count);
+ }
+ }
+ }
+
+ public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1, T2 arg2, T3 arg3) where TFormatter : ITextOutput
+ {
+ var reader = new CompositeFormatReader(compositeFormat);
+ while (true)
+ {
+ var segment = reader.Next();
+ if (segment == null) return;
+
+ if (segment.Value.Count == 0) // insertion point
+ {
+ if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format);
+ else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format);
+ else if (segment.Value.Index == 2) formatter.AppendUntyped(arg2, segment.Value.Format);
+ else if (segment.Value.Index == 3) formatter.AppendUntyped(arg3, segment.Value.Format);
+ else throw new Exception("invalid insertion point");
+ }
+ else // literal
+ {
+ formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count);
+ }
+ }
+ }
+
+ public static void Format(this TFormatter formatter, string compositeFormat, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) where TFormatter : ITextOutput
+ {
+ var reader = new CompositeFormatReader(compositeFormat);
+ while (true)
+ {
+ var segment = reader.Next();
+ if (segment == null) return;
+
+ if (segment.Value.Count == 0) // insertion point
+ {
+ if (segment.Value.Index == 0) formatter.AppendUntyped(arg0, segment.Value.Format);
+ else if (segment.Value.Index == 1) formatter.AppendUntyped(arg1, segment.Value.Format);
+ else if (segment.Value.Index == 2) formatter.AppendUntyped(arg2, segment.Value.Format);
+ else if (segment.Value.Index == 3) formatter.AppendUntyped(arg3, segment.Value.Format);
+ else if (segment.Value.Index == 4) formatter.AppendUntyped(arg4, segment.Value.Format);
+ else throw new Exception("invalid insertion point");
+ }
+ else // literal
+ {
+ formatter.Append(compositeFormat, segment.Value.Index, segment.Value.Count);
+ }
+ }
+ }
+
+ // TODO: this should be removed and an ability to append substrings should be added
+ static void Append(this TFormatter formatter, string whole, int index, int count) where TFormatter : ITextOutput
+ {
+ var buffer = formatter.Buffer;
+ var maxBytes = count << 4; // this is the worst case, i.e. 4 bytes per char
+ while(buffer.Length < maxBytes)
+ {
+ formatter.Enlarge(maxBytes);
+ buffer = formatter.Buffer;
+ }
+
+ // this should be optimized using fixed pointer to substring, but I will wait with this till we design proper substring
+
+ var characters = whole.Slice(index, count);
+ if (!formatter.TryAppend(characters, formatter.SymbolTable))
+ {
+ Debug.Assert(false, "this should never happen"); // because I pre-resized the buffer to 4 bytes per char at the top of this method.
+ }
+ }
+
+ static void AppendUntyped(this TFormatter formatter, T value, ParsedFormat format) where TFormatter : ITextOutput
+ {
+ #region Built in types
+ var i32 = value as int?;
+ if (i32 != null)
+ {
+ formatter.Append(i32.Value, format);
+ return;
+ }
+ var i64 = value as long?;
+ if (i64 != null)
+ {
+ formatter.Append(i64.Value, format);
+ return;
+ }
+ var i16 = value as short?;
+ if (i16 != null)
+ {
+ formatter.Append(i16.Value, format);
+ return;
+ }
+ var b = value as byte?;
+ if (b != null)
+ {
+ formatter.Append(b.Value, format);
+ return;
+ }
+ var c = value as char?;
+ if (c != null)
+ {
+ formatter.Append(c.Value);
+ return;
+ }
+ var u32 = value as uint?;
+ if (u32 != null)
+ {
+ formatter.Append(u32.Value, format);
+ return;
+ }
+ var u64 = value as ulong?;
+ if (u64 != null)
+ {
+ formatter.Append(u64.Value, format);
+ return;
+ }
+ var u16 = value as ushort?;
+ if (u16 != null)
+ {
+ formatter.Append(u16.Value, format);
+ return;
+ }
+ var sb = value as sbyte?;
+ if (sb != null)
+ {
+ formatter.Append(sb.Value, format);
+ return;
+ }
+ var str = value as string;
+ if (str != null)
+ {
+ formatter.Append(str);
+ return;
+ }
+ var dt = value as DateTime?;
+ if (dt != null)
+ {
+ formatter.Append(dt.Value, format);
+ return;
+ }
+ var dto = value as DateTimeOffset?;
+ if (dto != null) {
+ formatter.Append(dto.Value, format);
+ return;
+ }
+ var ts = value as TimeSpan?;
+ if (ts != null)
+ {
+ formatter.Append(ts.Value, format);
+ return;
+ }
+ var guid = value as Guid?;
+ if (guid != null) {
+ formatter.Append(guid.Value, format);
+ return;
+ }
+ #endregion
+
+ if (value is IBufferFormattable)
+ {
+ formatter.Append((IBufferFormattable)value, format); // this is boxing. not sure how to avoid it.
+ return;
+ }
+
+ throw new NotSupportedException("value is not formattable.");
+ }
+
+ // this is just a state machine walking the composite format and instructing CompositeFormattingExtensions.Format overloads on what to do.
+ // this whole type is not just a hacky prototype.
+ // I will clean it up later if I decide that I like this whole composite format model.
+ struct CompositeFormatReader
+ {
+ string _compositeFormatString;
+ int _currentIndex;
+ int _spanStart;
+ State _state;
+
+ public CompositeFormatReader(string format)
+ {
+ _compositeFormatString = format;
+ _currentIndex = 0;
+ _spanStart = 0;
+ _state = State.New;
+ }
+
+ public CompositeSegment? Next()
+ {
+ while (_currentIndex < _compositeFormatString.Length)
+ {
+ char c = _compositeFormatString[_currentIndex];
+ if (c == '{')
+ {
+ if (_state == State.Literal)
+ {
+ _state = State.New;
+ return CompositeSegment.Literal(_spanStart, _currentIndex);
+ }
+ if ((_currentIndex + 1 < _compositeFormatString.Length) && (_compositeFormatString[_currentIndex + 1] == c))
+ {
+ _state = State.Literal;
+ _currentIndex++;
+ _spanStart = _currentIndex;
+ }
+ else
+ {
+ _currentIndex++;
+ return ParseInsertionPoint();
+ }
+ }
+ else if (c == '}')
+ {
+ if ((_currentIndex + 1 < _compositeFormatString.Length) && (_compositeFormatString[_currentIndex + 1] == c))
+ {
+ if (_state == State.Literal)
+ {
+ _state = State.New;
+ return CompositeSegment.Literal(_spanStart, _currentIndex);
+ }
+ _state = State.Literal;
+ _currentIndex++;
+ _spanStart = _currentIndex;
+ }
+ else
+ {
+ throw new Exception("missing start bracket");
+ }
+ }
+ else
+ {
+ if (_state != State.Literal)
+ {
+ _state = State.Literal;
+ _spanStart = _currentIndex;
+ }
+ }
+ _currentIndex++;
+ }
+ if (_state == State.Literal)
+ {
+ _state = State.New;
+ return CompositeSegment.Literal(_spanStart, _currentIndex);
+ }
+ return null;
+ }
+
+ // this should be replaced with InvariantFormatter.Parse
+ static bool TryParse(string compositeFormat, int start, int count, out uint value, out int consumed)
+ {
+ consumed = 0;
+ value = 0;
+ for (int i = start; i < start + count; i++)
+ {
+ var digit = (byte)(compositeFormat[i] - '0');
+ if (digit >= 0 && digit <= 9)
+ {
+ value *= 10;
+ value += digit;
+ consumed++;
+ }
+ else
+ {
+ if (i == start) return false;
+ else return true;
+ }
+ }
+ return true;
+ }
+
+ CompositeSegment ParseInsertionPoint()
+ {
+ uint arg;
+ int consumed;
+ char? formatSpecifier = null;
+
+ if (!TryParse(_compositeFormatString, _currentIndex, 5, out arg, out consumed))
+ {
+ throw new Exception("invalid insertion point");
+ }
+ _currentIndex += consumed;
+ if (_currentIndex >= _compositeFormatString.Length)
+ {
+ throw new Exception("missing end bracket");
+ }
+
+ if(_compositeFormatString[_currentIndex] == ':')
+ {
+ _currentIndex++;
+ formatSpecifier = _compositeFormatString[_currentIndex];
+ _currentIndex++;
+ }
+
+ if (_compositeFormatString[_currentIndex] != '}')
+ {
+ throw new Exception("missing end bracket");
+ }
+
+ _currentIndex++;
+ var parsedFormat = (formatSpecifier.HasValue && formatSpecifier.Value != 0) ? new ParsedFormat(formatSpecifier.Value) : default;
+ return CompositeSegment.InsertionPoint(arg, parsedFormat);
+ }
+
+ public enum State : byte
+ {
+ New,
+ Literal,
+ InsertionPoint
+ }
+
+ public struct CompositeSegment
+ {
+ public ParsedFormat Format { get; private set; }
+ public int Index { get; private set; }
+ public int Count { get; private set; }
+
+ public static CompositeSegment InsertionPoint(uint argIndex, ParsedFormat format)
+ {
+ return new CompositeSegment() { Index = (int)argIndex, Format = format };
+ }
+
+ public static CompositeSegment Literal(int startIndex, int endIndex)
+ {
+ return new CompositeSegment() { Index = startIndex, Count = endIndex - startIndex };
+ }
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/ArrayFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/ArrayFormatter.cs
new file mode 100644
index 000000000..a63f358bc
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/ArrayFormatter.cs
@@ -0,0 +1,55 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+using System.Collections.Sequences;
+
+namespace System.Text.Formatting
+{
+ public class ArrayFormatter : ITextOutput
+ {
+ ResizableArray _buffer;
+ SymbolTable _symbolTable;
+ ArrayPool _pool;
+
+ public ArrayFormatter(int capacity, SymbolTable symbolTable, ArrayPool pool = null)
+ {
+ _pool = pool ?? ArrayPool.Shared;
+ _symbolTable = symbolTable;
+ _buffer = new ResizableArray(_pool.Rent(capacity));
+ }
+
+ public int CommitedByteCount => _buffer.Count;
+
+ public void Clear() {
+ _buffer.Count = 0;
+ }
+
+ public ArraySegment Free => _buffer.Free;
+ public ArraySegment Formatted => _buffer.Full;
+
+ public SymbolTable SymbolTable => _symbolTable;
+
+ public Span Buffer => Free.AsSpan();
+
+ void IOutput.Enlarge(int desiredBufferLength)
+ {
+ if (desiredBufferLength < 1) desiredBufferLength = 1;
+ var doubleCount = _buffer.Free.Count * 2;
+ int newSize = desiredBufferLength>doubleCount?desiredBufferLength:doubleCount;
+ var newArray = _pool.Rent(newSize + _buffer.Count);
+ var oldArray = _buffer.Resize(newArray);
+ _pool.Return(oldArray);
+ }
+
+ void IOutput.Advance(int bytes)
+ {
+ _buffer.Count += bytes;
+ if(_buffer.Count > _buffer.Count)
+ {
+ throw new InvalidOperationException("More bytes commited than returned from FreeBuffer");
+ }
+ }
+ }
+}
diff --git a/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/OutputFormatter.cs b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/OutputFormatter.cs
new file mode 100644
index 000000000..e745517bf
--- /dev/null
+++ b/src/Discord.Net.Core/_corefxlab/System.Text.Formatting/System/Text/Formatting/Formatters/OutputFormatter.cs
@@ -0,0 +1,32 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Buffers;
+
+namespace System.Text.Formatting
+{
+ public struct OutputFormatter : ITextOutput where TOutput : IOutput
+ {
+ TOutput _output;
+ SymbolTable _symbolTable;
+
+ public OutputFormatter(TOutput output, SymbolTable symbolTable)
+ {
+ _output = output;
+ _symbolTable = symbolTable;
+ }
+
+ public OutputFormatter(TOutput output) : this(output, SymbolTable.InvariantUtf8)
+ {
+ }
+
+ public Span