fix: Eliminates string allocations while writing gump packets (#1017)

* Introduces `RawInterpolatedStringHandler` which is exactly the same as `DefaultInterpolatedStringHandler` except it _unsafely exposes_ it's `ReadOnlySpan<char>` buffer. This is useful for writing the string's data without actually building the string.
* Uses this new string interpolation handler in `SpanWriter` to eliminate intermediate strings built. This is immensely useful in eliminating string allocations in writing Gump packets.
This commit is contained in:
Kamron Batman 2022-05-10 18:50:23 -07:00 • committed by GitHub
parent f7cbeacf48
commit 87b63b38a5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 674 additions and 43 deletions

View file

@ -16,6 +16,7 @@
using System.Buffers.Binary;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@ -275,21 +276,52 @@ public ref struct SpanWriter
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(char chr) => Write((byte)chr);
public void WriteString<T>(string value, Encoding encoding, int fixedLength = -1) where T : struct, IEquatable<T>
public void WriteAscii(
ref RawInterpolatedStringHandler handler)
{
int sizeT = Unsafe.SizeOf<T>();
Write(handler.Text, Encoding.ASCII);
handler.Clear();
}
if (sizeT > 2)
public void WriteAscii(
IFormatProvider? formatProvider,
[InterpolatedStringHandlerArgument("formatProvider")]
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, Encoding.ASCII);
handler.Clear();
}
public void Write(
Encoding encoding,
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, encoding);
handler.Clear();
}
public void Write(
Encoding encoding,
IFormatProvider? formatProvider,
[InterpolatedStringHandlerArgument("formatProvider")]
ref RawInterpolatedStringHandler handler)
{
Write(handler.Text, encoding);
handler.Clear();
}
public void Write(ReadOnlySpan<char> value, Encoding encoding, int fixedLength = -1)
{
var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length);
var src = value[..charLength];
var byteLength = encoding.GetByteLengthForEncoding();
var byteCount = encoding.GetByteCount(src);
if (fixedLength > src.Length)
{
throw new InvalidConstraintException("WriteString only accepts byte, sbyte, char, short, and ushort as a constraint");
byteCount += (fixedLength - src.Length) * byteLength;
}
value ??= string.Empty;
var charLength = Math.Min(fixedLength > -1 ? fixedLength : value.Length, value.Length);
var src = value.AsSpan(0, charLength);
var byteCount = fixedLength > -1 ? fixedLength * sizeT : encoding.GetByteCount(value);
if (byteCount == 0)
{
return;
@ -302,7 +334,7 @@ public ref struct SpanWriter
if (fixedLength > -1)
{
var extra = fixedLength * sizeT - bytesWritten;
var extra = fixedLength * byteLength - bytesWritten;
if (extra > 0)
{
Clear(extra);
@ -311,53 +343,53 @@ public ref struct SpanWriter
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value) => WriteString<char>(value, TextEncoding.UnicodeLE);
public void WriteLittleUni(string value) => Write(value, TextEncoding.UnicodeLE);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUniNull(string value)
{
WriteString<char>(value, TextEncoding.UnicodeLE);
Write(value, TextEncoding.UnicodeLE);
Write((ushort)0);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteLittleUni(string value, int fixedLength) => WriteString<char>(value, TextEncoding.UnicodeLE, fixedLength);
public void WriteLittleUni(string value, int fixedLength) => Write(value, TextEncoding.UnicodeLE, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value) => WriteString<char>(value, TextEncoding.Unicode);
public void WriteBigUni(string value) => Write(value, TextEncoding.Unicode);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUniNull(string value)
{
WriteString<char>(value, TextEncoding.Unicode);
Write(value, TextEncoding.Unicode);
Write((ushort)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteBigUni(string value, int fixedLength) => WriteString<char>(value, TextEncoding.Unicode, fixedLength);
public void WriteBigUni(string value, int fixedLength) => Write(value, TextEncoding.Unicode, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8(string value) => WriteString<byte>(value, TextEncoding.UTF8);
public void WriteUTF8(string value) => Write(value, TextEncoding.UTF8);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteUTF8Null(string value)
{
WriteString<byte>(value, TextEncoding.UTF8);
Write(value, TextEncoding.UTF8);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value) => WriteString<byte>(value, Encoding.ASCII);
public void WriteAscii(string value) => Write(value, Encoding.ASCII);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAsciiNull(string value)
{
WriteString<byte>(value, Encoding.ASCII);
Write(value, Encoding.ASCII);
Write((byte)0); // '\0'
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void WriteAscii(string value, int fixedLength) => WriteString<byte>(value, Encoding.ASCII, fixedLength);
public void WriteAscii(string value, int fixedLength) => Write(value, Encoding.ASCII, fixedLength);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear(int count)