fix(core): Optimizes strings / .NET 5 compatibility changes (#354)

- [X] Removes some string allocations (e.g. split)
- [X] Optimizes some collections
- [X] Converts insensitive to extension methods of built-ins.
- [X] Adds ordinal (case sensitive) string helpers
- [X] Fixes conditionals for in-game commands so they use Ordinal comparisons.
- [X] Replaces ToLower.Contains with InsensitiveContains
- [X] Adds ValueStringBuilder
- [X] Implements ValueStringBuilder in a few places where it makes sense
- [X] Removes the redundant Wrap function and replaces it with an optimized version
- [X] Fixes list conversions in Utility

Closes #351

Bumps release version
This commit is contained in:
Kamron Batman 2020-12-20 23:21:55 -08:00 • committed by GitHub
parent 92aae8d482
commit 77ce2e1980
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
117 changed files with 2000 additions and 1226 deletions

View file

@ -1,118 +0,0 @@
using System;
using System.IO;
using System.Text;
using Server.Network;
using Xunit;
namespace Server.Tests.Network
{
public class CircularBufferReaderTests
{
[Theory, InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 0),
InlineData("Test String", "utf-u", false, -1, 1024, 1024, 0),
InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 0),
InlineData("Test String", "utf-16", false, -1, 1024, 1024, 0),
InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 1030),
InlineData("Test String", "utf-u", false, -1, 1024, 1024, 1030),
InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 1030),
InlineData("Test String", "utf-16", false, -1, 1024, 1024, 1030),
InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 1020),
InlineData("Test String", "utf-u", false, -1, 1024, 1024, 1020),
InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 1020),
InlineData("Test String", "utf-16", false, -1, 1024, 1024, 1020),
InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 0),
InlineData("Test String", "utf-u", false, 8, 1024, 1024, 0),
InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 0),
InlineData("Test String", "utf-16", false, 8, 1024, 1024, 0),
InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 1030),
InlineData("Test String", "utf-u", false, 8, 1024, 1024, 1030),
InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 1030),
InlineData("Test String", "utf-16", false, 8, 1024, 1024, 1030),
InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 1020),
InlineData("Test String", "utf-u", false, 8, 1024, 1024, 1020),
InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 1020),
InlineData("Test String", "utf-16", false, 8, 1024, 1024, 1020),
InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 0),
InlineData("Test String", "utf-u", false, 20, 1024, 1024, 0),
InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 0),
InlineData("Test String", "utf-16", false, 20, 1024, 1024, 0),
InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 1030),
InlineData("Test String", "utf-u", false, 20, 1024, 1024, 1030),
InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 1030),
InlineData("Test String", "utf-16", false, 20, 1024, 1024, 1030),
InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 1020),
InlineData("Test String", "utf-u", false, 20, 1024, 1024, 1020),
InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 1020),
InlineData("Test String", "utf-16", false, 20, 1024, 1024, 1020)]
// First only, beginning
// Second only
// Split
// First only, beginning, fixed length smaller
// Second only, fixed length smaller
// Split, fixed length smaller
// First only, beginning, fixed length bigger
// Second only, fixed length bigger
// Split, fixed length bigger
public void TestReadString(
string value,
string encodingStr,
bool isSafe,
int fixedLength,
int firstSize,
int secondSize,
int offset
)
{
Span<byte> buffer = stackalloc byte[firstSize + secondSize];
buffer.Clear();
var encoding = EncodingHelpers.GetEncoding(encodingStr);
var strLength = fixedLength > -1 ? Math.Min(value.Length, fixedLength) : value.Length;
var chars = value.AsSpan(0, strLength);
;
encoding.GetBytes(chars, buffer.Slice(offset));
var reader = new CircularBufferReader(buffer.Slice(0, firstSize), buffer.Slice(firstSize));
reader.Seek(offset, SeekOrigin.Begin);
var actual = reader.ReadString(encoding, isSafe, fixedLength);
Assert.Equal(value.Substring(0, strLength), actual);
}
[Fact]
public void TestReadStringBetween()
{
Span<byte> expected = stackalloc byte[19];
expected[0] = 0x1;
expected[1] = 0x1;
expected[2] = 0x1;
expected[3] = 0x1;
Encoding.ASCII.GetBytes("TestString", expected.Slice(4, 10));
expected[14] = 0x0; // Null
expected[15] = 0x2;
expected[16] = 0x2;
expected[17] = 0x2;
expected[18] = 0x2;
var reader = new CircularBufferReader(expected, stackalloc byte[0]);
var num1 = reader.ReadInt32();
var str = reader.ReadAscii();
var num2 = reader.ReadInt32();
Assert.Equal(0x01010101, num1);
Assert.Equal("TestString", str);
Assert.Equal(0x02020202, num2);
}
}
}

View file

@ -1,92 +0,0 @@
using System;
using System.Buffers;
using System.IO;
using Xunit;
namespace Server.Tests.Buffers
{
public class CircularBufferWriterTests
{
[Theory, InlineData("Test String", "us-ascii", -1, 1024, 1024, 0),
InlineData("Test String", "utf-8", -1, 1024, 1024, 0), InlineData("Test String", "utf-16BE", -1, 1024, 1024, 0),
InlineData("Test String", "utf-16", -1, 1024, 1024, 0), InlineData("Test String", "us-ascii", -1, 1024, 1024, 1030),
InlineData("Test String", "utf-8", -1, 1024, 1024, 1030),
InlineData("Test String", "utf-16BE", -1, 1024, 1024, 1030),
InlineData("Test String", "utf-16", -1, 1024, 1024, 1030),
InlineData("Test String", "us-ascii", -1, 1024, 1024, 1020),
InlineData("Test String", "utf-8", -1, 1024, 1024, 1020),
InlineData("Test String", "utf-16BE", -1, 1024, 1024, 1020),
InlineData("Test String", "utf-16", -1, 1024, 1024, 1020), InlineData("Test String", "us-ascii", 8, 1024, 1024, 0),
InlineData("Test String", "utf-16BE", 8, 1024, 1024, 0), InlineData("Test String", "utf-16", 8, 1024, 1024, 0),
InlineData("Test String", "us-ascii", 8, 1024, 1024, 1030),
InlineData("Test String", "utf-16BE", 8, 1024, 1024, 1030),
InlineData("Test String", "utf-16", 8, 1024, 1024, 1030),
InlineData("Test String", "us-ascii", 8, 1024, 1024, 1020),
InlineData("Test String", "utf-16BE", 8, 1024, 1024, 1020),
InlineData("Test String", "utf-16", 8, 1024, 1024, 1020), InlineData("Test String", "us-ascii", 20, 1024, 1024, 0),
InlineData("Test String", "utf-16BE", 20, 1024, 1024, 0), InlineData("Test String", "utf-16", 20, 1024, 1024, 0),
InlineData("Test String", "us-ascii", 20, 1024, 1024, 1030),
InlineData("Test String", "utf-16BE", 20, 1024, 1024, 1030),
InlineData("Test String", "utf-16", 20, 1024, 1024, 1030),
InlineData("Test String", "us-ascii", 20, 1024, 1024, 1020),
InlineData("Test String", "utf-16BE", 20, 1024, 1024, 1020),
InlineData("Test String", "utf-16", 20, 1024, 1024, 1020)]
// First only, beginning
// Second only
// Split
// First only, beginning, fixed length smaller
// Second only, fixed length smaller
// Split, fixed length smaller
// First only, beginning, fixed length bigger
// Second only, fixed length bigger
// Split, fixed length bigger
public void TestWriteString(
string value,
string encodingStr,
int fixedLength,
int firstSize,
int secondSize,
int offset
)
{
Span<byte> buffer = stackalloc byte[firstSize + secondSize];
buffer.Clear();
var encoding = EncodingHelpers.GetEncoding(encodingStr);
var strLength = fixedLength > -1 ? Math.Min(value.Length, fixedLength) : value.Length;
var chars = value.AsSpan(0, strLength);
var writer = new CircularBufferWriter(buffer.Slice(0, firstSize), buffer.Slice(firstSize));
writer.Seek(offset, SeekOrigin.Begin);
writer.WriteString(chars, encoding);
if (offset > 0)
{
Span<byte> testEmpty = stackalloc byte[offset];
testEmpty.Clear();
AssertThat.Equal(buffer.Slice(0, offset), testEmpty);
}
Span<byte> expectedStr = stackalloc byte[encoding.GetByteCount(chars)];
encoding.GetBytes(chars, expectedStr.Slice(0));
AssertThat.Equal(buffer.Slice(offset, expectedStr.Length), expectedStr);
offset += expectedStr.Length;
if (offset < buffer.Length)
{
Span<byte> testEmpty = stackalloc byte[buffer.Length - offset];
testEmpty.Clear();
AssertThat.Equal(buffer.Slice(offset), testEmpty);
}
}
}
}

View file

@ -0,0 +1,102 @@
using System;
using System.IO;
using System.Text;
using Server.Network;
using Xunit;
namespace Server.Tests.Network
{
public class CircularBufferReaderTests
{
[Theory]
[InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 0)]
[InlineData("Test String", "utf-u", false, -1, 1024, 1024, 0)]
[InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 0)]
[InlineData("Test String", "utf-16", false, -1, 1024, 1024, 0)]
[InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 1030)]
[InlineData("Test String", "utf-u", false, -1, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16", false, -1, 1024, 1024, 1030)]
[InlineData("Test String", "us-ascii", false, -1, 1024, 1024, 1020)]
[InlineData("Test String", "utf-u", false, -1, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16BE", false, -1, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16", false, -1, 1024, 1024, 1020)]
[InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 0)]
[InlineData("Test String", "utf-u", false, 8, 1024, 1024, 0)]
[InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 0)]
[InlineData("Test String", "utf-16", false, 8, 1024, 1024, 0)]
[InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 1030)]
[InlineData("Test String", "utf-u", false, 8, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16", false, 8, 1024, 1024, 1030)]
[InlineData("Test String", "us-ascii", false, 8, 1024, 1024, 1020)]
[InlineData("Test String", "utf-u", false, 8, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16BE", false, 8, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16", false, 8, 1024, 1024, 1020)]
[InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 0)]
[InlineData("Test String", "utf-u", false, 20, 1024, 1024, 0)]
[InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 0)]
[InlineData("Test String", "utf-16", false, 20, 1024, 1024, 0)]
[InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 1030)]
[InlineData("Test String", "utf-u", false, 20, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16", false, 20, 1024, 1024, 1030)]
[InlineData("Test String", "us-ascii", false, 20, 1024, 1024, 1020)]
[InlineData("Test String", "utf-u", false, 20, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16BE", false, 20, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16", false, 20, 1024, 1024, 1020)]
public void TestReadString(
string value,
string encodingStr,
bool isSafe,
int fixedLength,
int firstSize,
int secondSize,
int offset
)
{
Span<byte> buffer = stackalloc byte[firstSize + secondSize];
buffer.Clear();
var encoding = EncodingHelpers.GetEncoding(encodingStr);
var strLength = fixedLength > -1 ? Math.Min(value.Length, fixedLength) : value.Length;
var chars = value.AsSpan(0, strLength);
;
encoding.GetBytes(chars, buffer.Slice(offset));
var reader = new CircularBufferReader(buffer.Slice(0, firstSize), buffer.Slice(firstSize));
reader.Seek(offset, SeekOrigin.Begin);
var actual = reader.ReadString(encoding, isSafe, fixedLength);
Assert.Equal(value.Substring(0, strLength), actual);
}
[Fact]
public void TestReadStringBetween()
{
Span<byte> expected = stackalloc byte[19];
expected[0] = 0x1;
expected[1] = 0x1;
expected[2] = 0x1;
expected[3] = 0x1;
Encoding.ASCII.GetBytes("TestString", expected.Slice(4, 10));
expected[14] = 0x0; // Null
expected[15] = 0x2;
expected[16] = 0x2;
expected[17] = 0x2;
expected[18] = 0x2;
var reader = new CircularBufferReader(expected, stackalloc byte[0]);
var num1 = reader.ReadInt32();
var str = reader.ReadAscii();
var num2 = reader.ReadInt32();
Assert.Equal(0x01010101, num1);
Assert.Equal("TestString", str);
Assert.Equal(0x02020202, num2);
}
}
}

View file

@ -0,0 +1,82 @@
using System;
using System.Buffers;
using System.IO;
using Xunit;
namespace Server.Tests.Buffers
{
public class CircularBufferWriterTests
{
[Theory]
[InlineData("Test String", "us-ascii", -1, 1024, 1024, 0)]
[InlineData("Test String", "utf-8", -1, 1024, 1024, 0)]
[InlineData("Test String", "utf-16BE", -1, 1024, 1024, 0)]
[InlineData("Test String", "utf-16", -1, 1024, 1024, 0)]
[InlineData("Test String", "us-ascii", -1, 1024, 1024, 1030)]
[InlineData("Test String", "utf-8", -1, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16BE", -1, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16", -1, 1024, 1024, 1030)]
[InlineData("Test String", "us-ascii", -1, 1024, 1024, 1020)]
[InlineData("Test String", "utf-8", -1, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16BE", -1, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16", -1, 1024, 1024, 1020)]
[InlineData("Test String", "us-ascii", 8, 1024, 1024, 0)]
[InlineData("Test String", "utf-16BE", 8, 1024, 1024, 0)]
[InlineData("Test String", "utf-16", 8, 1024, 1024, 0)]
[InlineData("Test String", "us-ascii", 8, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16BE", 8, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16", 8, 1024, 1024, 1030)]
[InlineData("Test String", "us-ascii", 8, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16BE", 8, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16", 8, 1024, 1024, 1020)]
[InlineData("Test String", "us-ascii", 20, 1024, 1024, 0)]
[InlineData("Test String", "utf-16BE", 20, 1024, 1024, 0)]
[InlineData("Test String", "utf-16", 20, 1024, 1024, 0)]
[InlineData("Test String", "us-ascii", 20, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16BE", 20, 1024, 1024, 1030)]
[InlineData("Test String", "utf-16", 20, 1024, 1024, 1030)]
[InlineData("Test String", "us-ascii", 20, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16BE", 20, 1024, 1024, 1020)]
[InlineData("Test String", "utf-16", 20, 1024, 1024, 1020)]
public void TestWriteString(
string value,
string encodingStr,
int fixedLength,
int firstSize,
int secondSize,
int offset
)
{
Span<byte> buffer = stackalloc byte[firstSize + secondSize];
buffer.Clear();
var encoding = EncodingHelpers.GetEncoding(encodingStr);
var strLength = fixedLength > -1 ? Math.Min(value.Length, fixedLength) : value.Length;
var chars = value.AsSpan(0, strLength);
var writer = new CircularBufferWriter(buffer.Slice(0, firstSize), buffer.Slice(firstSize));
writer.Seek(offset, SeekOrigin.Begin);
writer.WriteString(chars, encoding);
if (offset > 0)
{
Span<byte> testEmpty = stackalloc byte[offset];
testEmpty.Clear();
AssertThat.Equal(buffer.Slice(0, offset), testEmpty);
}
Span<byte> expectedStr = stackalloc byte[encoding.GetByteCount(chars)];
encoding.GetBytes(chars, expectedStr.Slice(0));
AssertThat.Equal(buffer.Slice(offset, expectedStr.Length), expectedStr);
offset += expectedStr.Length;
if (offset < buffer.Length)
{
Span<byte> testEmpty = stackalloc byte[buffer.Length - offset];
testEmpty.Clear();
AssertThat.Equal(buffer.Slice(offset), testEmpty);
}
}
}
}

View file

@ -0,0 +1,21 @@
using Server.Buffers;
using Xunit;
namespace Server.Tests.Buffers
{
public class ValueStringBuilderTests
{
[Theory]
[InlineData("Admin Kamron", "Kamron", 0, 6)]
[InlineData("Admin Kamron", "Admin ron", 6, 3)]
[InlineData("Admin Kamron", "Admin", 5, 7)]
public void TestRemove(string original, string removed, int startIndex, int length)
{
using var sb = new ValueStringBuilder(stackalloc char[64]);
sb.Append(original);
sb.Remove(startIndex, length);
Assert.Equal(removed, sb.ToString());
}
}
}

View file

@ -1,16 +1,124 @@
using System;
using System.Collections.Generic;
using Xunit;
namespace Server.Tests
{
public class TestStringHelpers
{
[Theory, InlineData(null, "default value", "default value"), InlineData("", "default value", "default value"),
InlineData("this is a valid string", "default value", "this is a valid string")]
[Theory]
[InlineData(null, "default value", "default value")]
[InlineData("", "default value", "default value")]
[InlineData("this is a valid string", "default value", "this is a valid string")]
public void TestIsNullOrDefault(string value, string defaultValue, string expected)
{
var actual = value.DefaultIfNullOrEmpty(defaultValue);
Assert.Equal(expected, actual);
}
[Theory]
[InlineData("this is not capitalized", "This Is Not Capitalized")]
[InlineData("", "")]
[InlineData(null, null)]
[InlineData("nospaceshere", "Nospaceshere")]
[InlineData("harry the fireman", "Harry the Fireman")]
public void TestCapitalize(string original, string capitalized)
{
var actual = original.Capitalize();
Assert.Equal(capitalized, actual);
}
[Theory]
[InlineData("we are testing removing spaces", " ", "wearetestingremovingspaces", StringComparison.Ordinal)]
[InlineData("", " ", "", StringComparison.Ordinal)]
[InlineData(null, null, null, StringComparison.Ordinal)]
public void TestRemove(string original, string separator, string removed, StringComparison comparison)
{
var actual = original.AsSpan().Remove(separator, comparison);
Assert.Equal(removed, actual);
}
[Theory]
[InlineData("this is a sentence that will probably wrap around a few times because it is long", 10, 6)]
public void TestWrap(string sentence, int perLine, int maxLines)
{
var expected = OldWrap(sentence, perLine, maxLines);
var actual = sentence.Wrap(perLine, maxLines);
Assert.Equal(expected, actual);
}
// The old wrap function from HouseGump/HouseGumpAOS
private static List<string> OldWrap(string value, int startIndex, int maxLines)
{
if (value == null || (value = value.Trim()).Length <= 0)
{
return null;
}
var values = value.Split(' ');
var list = new List<string>();
var current = "";
for (var i = 0; i < values.Length; ++i)
{
var val = values[i];
var v = current.Length == 0 ? val : $"{current} {val}";
if (v.Length < startIndex)
{
current = v;
}
else if (v.Length == startIndex)
{
list.Add(v);
if (list.Count == maxLines)
{
return list;
}
current = "";
}
else if (val.Length <= startIndex)
{
list.Add(current);
if (list.Count == maxLines)
{
return list;
}
current = val;
}
else
{
while (v.Length >= startIndex)
{
list.Add(v.Substring(0, startIndex));
if (list.Count == maxLines)
{
return list;
}
v = v.Substring(startIndex);
}
current = v;
}
}
if (current.Length > 0)
{
list.Add(current);
}
return list;
}
}
}