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

@ -0,0 +1,363 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
namespace Server.Buffers
{
public ref struct ValueStringBuilder
{
private char[]? _arrayToReturnToPool;
private Span<char> _chars;
public ValueStringBuilder(Span<char> initialBuffer)
{
_arrayToReturnToPool = null;
_chars = initialBuffer;
Length = 0;
}
public ValueStringBuilder(int initialCapacity)
{
_arrayToReturnToPool = ArrayPool<char>.Shared.Rent(initialCapacity);
_chars = _arrayToReturnToPool;
Length = 0;
}
public int Length { get; set; }
public int Capacity => _chars.Length;
public void EnsureCapacity(int capacity)
{
if (capacity > _chars.Length)
{
Grow(capacity - Length);
}
}
/// <summary>
/// Get a pinnable reference to the builder.
/// Does not ensure there is a null char after <see cref="Length"/>
/// This overload is pattern matched in the C# 7.3+ compiler so you can omit
/// the explicit method call, and write eg "fixed (char* c = builder)"
/// </summary>
public ref char GetPinnableReference() => ref MemoryMarshal.GetReference(_chars);
/// <summary>
/// Get a pinnable reference to the builder.
/// </summary>
/// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
public ref char GetPinnableReference(bool terminate)
{
if (terminate)
{
EnsureCapacity(Length + 1);
_chars[Length] = '\0';
}
return ref MemoryMarshal.GetReference(_chars);
}
public ref char this[int index] => ref _chars[index];
public override string ToString() => _chars.Slice(0, Length).ToString();
/// <summary>Returns the underlying storage of the builder.</summary>
public Span<char> RawChars => _chars;
/// <summary>
/// Returns a span around the contents of the builder.
/// </summary>
/// <param name="terminate">Ensures that the builder has a null char after <see cref="Length"/></param>
public ReadOnlySpan<char> AsSpan(bool terminate)
{
if (terminate)
{
EnsureCapacity(Length + 1);
_chars[Length] = '\0';
}
return _chars.Slice(0, Length);
}
public ReadOnlySpan<char> AsSpan() => _chars.Slice(0, Length);
public ReadOnlySpan<char> AsSpan(int start) => _chars.Slice(start, Length - start);
public ReadOnlySpan<char> AsSpan(int start, int length) => _chars.Slice(start, length);
public bool TryCopyTo(Span<char> destination, out int charsWritten)
{
if (_chars.Slice(0, Length).TryCopyTo(destination))
{
charsWritten = Length;
return true;
}
charsWritten = 0;
return false;
}
public void Insert(int index, char value, int count)
{
if (Length > _chars.Length - count)
{
Grow(count);
}
int remaining = Length - index;
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
_chars.Slice(index, count).Fill(value);
Length += count;
}
public void Insert(int index, string? s)
{
if (s == null)
{
return;
}
int count = s.Length;
if (Length > _chars.Length - count)
{
Grow(count);
}
int remaining = Length - index;
_chars.Slice(index, remaining).CopyTo(_chars.Slice(index + count));
s.AsSpan().CopyTo(_chars.Slice(index));
Length += count;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(char c)
{
int pos = Length;
if ((uint)pos < (uint)_chars.Length)
{
_chars[pos] = c;
Length = pos + 1;
}
else
{
GrowAndAppend(c);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Append(string? s)
{
if (s == null)
{
return;
}
int pos = Length;
if (s.Length == 1 && (uint)pos < (uint)_chars.Length) // very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
{
_chars[pos] = s[0];
Length = pos + 1;
}
else
{
AppendSlow(s);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void AppendLine(string? s)
{
if (s == null)
{
return;
}
// very common case, e.g. appending strings from NumberFormatInfo like separators, percent symbols, etc.
if (s.Length == 1)
{
Append(s[0]);
}
else
{
AppendSlow(s);
}
Append(Environment.NewLine);
}
private void AppendSlow(string s)
{
int pos = Length;
if (pos > _chars.Length - s.Length)
{
Grow(s.Length);
}
s.AsSpan().CopyTo(_chars.Slice(pos));
Length += s.Length;
}
public void Append(char c, int count)
{
if (Length > _chars.Length - count)
{
Grow(count);
}
Span<char> dst = _chars.Slice(Length, count);
for (int i = 0; i < dst.Length; i++)
{
dst[i] = c;
}
Length += count;
}
public unsafe void Append(char* value, int length)
{
int pos = Length;
if (pos > _chars.Length - length)
{
Grow(length);
}
Span<char> dst = _chars.Slice(Length, length);
for (int i = 0; i < dst.Length; i++)
{
dst[i] = *value++;
}
Length += length;
}
public void Append(ReadOnlySpan<char> value)
{
int pos = Length;
if (pos > _chars.Length - value.Length)
{
Grow(value.Length);
}
value.CopyTo(_chars.Slice(Length));
Length += value.Length;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public Span<char> AppendSpan(int length)
{
int origPos = Length;
if (origPos > _chars.Length - length)
{
Grow(length);
}
Length = origPos + length;
return _chars.Slice(origPos, length);
}
[MethodImpl(MethodImplOptions.NoInlining)]
private void GrowAndAppend(char c)
{
Grow(1);
Append(c);
}
/// <summary>
/// Resize the internal buffer either by doubling current buffer size or
/// by adding <paramref name="additionalCapacityBeyondPos"/> to
/// <see cref="Length"/> whichever is greater.
/// </summary>
/// <param name="additionalCapacityBeyondPos">
/// Number of chars requested beyond current position.
/// </param>
[MethodImpl(MethodImplOptions.NoInlining)]
private void Grow(int additionalCapacityBeyondPos)
{
char[] poolArray = ArrayPool<char>.Shared.Rent(Math.Max(Length + additionalCapacityBeyondPos, _chars.Length * 2));
_chars.Slice(0, Length).CopyTo(poolArray);
char[]? toReturn = _arrayToReturnToPool;
_chars = _arrayToReturnToPool = poolArray;
if (toReturn != null)
{
ArrayPool<char>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Dispose()
{
char[]? toReturn = _arrayToReturnToPool;
this = default; // for safety, to avoid using pooled array if this instance is erroneously appended to again
if (toReturn != null)
{
ArrayPool<char>.Shared.Return(toReturn);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Replace(char oldChar, char newChar, int startIndex, int count)
{
int currentLength = Length;
if ((uint)startIndex > (uint)currentLength)
{
throw new ArgumentOutOfRangeException(nameof(startIndex));
}
if (count < 0 || startIndex > currentLength - count)
{
throw new ArgumentOutOfRangeException(nameof(count));
}
var slice = _chars;
while (true)
{
var indexOf = slice.IndexOf(oldChar);
if (indexOf == -1)
{
break;
}
slice[indexOf] = newChar;
slice = slice.Slice(indexOf + 1);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Remove(int startIndex, int length)
{
if (length < 0)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
if (startIndex < 0)
{
throw new ArgumentOutOfRangeException(nameof(startIndex));
}
if (length > Length - startIndex)
{
throw new ArgumentOutOfRangeException(nameof(length));
}
if (startIndex == 0)
{
_chars = _chars.Slice(length);
}
else if (startIndex + length == Length)
{
_chars = _chars.Slice(0, startIndex);
}
else
{
// Somewhere in the middle, this will be slow
_chars.Slice(startIndex + length).CopyTo(_chars.Slice(startIndex));
}
Length -= length;
}
}
}

View file

@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.Text;
using Server.Buffers;
namespace Server
{
@ -32,7 +33,7 @@ namespace Server
try
{
var br1 = fmt.IndexOf('.', StringComparison.Ordinal);
var br1 = fmt.IndexOfOrdinal('.');
var br2 = fmt.IndexOf('.', br1 + 1);
var br3 = br2 + 1;
@ -60,15 +61,15 @@ namespace Server
}
}
if (fmt.Contains("god", StringComparison.Ordinal) || fmt.Contains("gq", StringComparison.Ordinal))
if (fmt.InsensitiveContains("god") || fmt.InsensitiveContains("gq"))
{
Type = ClientType.God;
}
else if (fmt.Contains("third dawn", StringComparison.Ordinal) ||
fmt.Contains("uo:td", StringComparison.Ordinal) ||
fmt.Contains("uotd", StringComparison.Ordinal) ||
fmt.Contains("uo3d", StringComparison.Ordinal) ||
fmt.Contains("uo:3d", StringComparison.Ordinal))
else if (fmt.InsensitiveContains("third dawn") ||
fmt.InsensitiveContains("uo:td") ||
fmt.InsensitiveContains("uotd") ||
fmt.InsensitiveContains("uo3d") ||
fmt.InsensitiveContains("uo:3d"))
{
Type = ClientType.UOTD;
}
@ -178,13 +179,13 @@ namespace Server
private string ToStringImpl()
{
var builder = new StringBuilder(16);
using var builder = new ValueStringBuilder(stackalloc char[32]);
builder.Append(Major);
builder.Append(Major.ToString());
builder.Append('.');
builder.Append(Minor);
builder.Append(Minor.ToString());
builder.Append('.');
builder.Append(Revision);
builder.Append(Revision.ToString());
if (Major <= 5 && Minor <= 0 && Revision <= 6) // Anything before 5.0.7
{
@ -196,7 +197,7 @@ namespace Server
else
{
builder.Append('.');
builder.Append(Patch);
builder.Append(Patch.ToString());
}
if (Type != ClientType.Regular)

View file

@ -188,7 +188,7 @@ namespace Server
public static bool Handle(Mobile from, string text, MessageType type = MessageType.Regular)
{
if (!text.StartsWith(Prefix, StringComparison.Ordinal) && type != MessageType.Command)
if (!text.StartsWithOrdinal(Prefix) && type != MessageType.Command)
{
return false;
}
@ -198,7 +198,7 @@ namespace Server
text = text.Substring(Prefix.Length);
}
var indexOf = text.IndexOf(' ', StringComparison.Ordinal);
var indexOf = text.IndexOfOrdinal(' ');
string command;
string[] args;

View file

@ -266,7 +266,7 @@ namespace Server
break;
}
if (!ipStr.Contains(':', StringComparison.Ordinal))
if (!ipStr.ContainsOrdinal(':'))
{
ipStr += ":2593";
}

View file

@ -55,7 +55,7 @@ namespace Server
public static Point2D Parse(string value)
{
var start = value.IndexOf('(', StringComparison.Ordinal);
var start = value.IndexOfOrdinal('(');
var end = value.IndexOf(',', start + 1);
Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x);

View file

@ -77,7 +77,7 @@ namespace Server
public static Point3D Parse(string value)
{
var start = value.IndexOf('(', StringComparison.Ordinal);
var start = value.IndexOfOrdinal('(');
var end = value.IndexOf(',', start + 1);
Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x);

View file

@ -49,7 +49,7 @@ namespace Server
public static Rectangle2D Parse(string value)
{
var start = value.IndexOf('(', StringComparison.Ordinal);
var start = value.IndexOfOrdinal('(');
var end = value.IndexOf(',', start + 1);
Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x);

View file

@ -142,7 +142,7 @@ namespace Server
public static WorldLocation Parse(string value)
{
var start = value.IndexOf('(', StringComparison.Ordinal);
var start = value.IndexOfOrdinal('(');
var end = value.IndexOf(',', start + 1);
Utility.ToInt32(value.Substring(start + 1, end - (start + 1)).Trim(), out var x);

View file

@ -1,4 +1,3 @@
using System;
using System.Collections.Generic;
using System.Linq;
@ -55,16 +54,26 @@ namespace Server.Guilds
public static BaseGuild FindByAbbrev(string abbr) =>
World.Guilds.Values.FirstOrDefault(g => g.Abbreviation == abbr);
public static List<BaseGuild> Search(string find)
public static HashSet<BaseGuild> Search(string find)
{
var words = find.ToLower().Split(' ');
var results = new List<BaseGuild>();
var results = new HashSet<BaseGuild>();
foreach (var g in World.Guilds.Values)
{
var name = g.Name.ToLower();
var name = g.Name;
if (words.All(t => name.IndexOf(t, StringComparison.Ordinal) != -1))
bool all = true;
foreach (var t in words)
{
if (name.InsensitiveIndexOf(t) == -1)
{
all = false;
break;
}
}
if (all)
{
results.Add(g);
}

View file

@ -1,24 +0,0 @@
using System;
using System.Collections.Generic;
namespace Server
{
public static class Insensitive
{
public static IComparer<string> Comparer { get; } = StringComparer.OrdinalIgnoreCase;
public static int Compare(string a, string b) => Comparer.Compare(a, b);
public static bool Equals(string a, string b) =>
a == null && b == null || a != null && b != null && a.Length == b.Length && Comparer.Compare(a, b) == 0;
public static bool StartsWith(string a, string b) =>
a != null && b != null && a.Length >= b.Length && Comparer.Compare(a.Substring(0, b.Length), b) == 0;
public static bool EndsWith(string a, string b) =>
a != null && b != null && a.Length >= b.Length && Comparer.Compare(a.Substring(a.Length - b.Length), b) == 0;
public static bool Contains(string a, string b) =>
a != null && b != null && a.Length >= b.Length && a.Contains(b, StringComparison.Ordinal);
}
}

View file

@ -1823,7 +1823,7 @@ namespace Server.Items
{
line = line.Trim();
if (line.Length == 0 || line.StartsWith("#", StringComparison.Ordinal))
if (line.Length == 0 || line.StartsWithOrdinal("#"))
{
continue;
}

View file

@ -13,6 +13,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using Server;
namespace System.Text.Json.Serialization
{
public class NullableStructSerializerFactory : JsonConverterFactory
@ -26,8 +28,7 @@ namespace System.Text.Json.Serialization
var structType = typeToConvert.GenericTypeArguments[0];
return !structType.IsPrimitive &&
structType.Namespace?.StartsWith(nameof(System), StringComparison.Ordinal) != true &&
!structType.IsEnum;
structType.Namespace.StartsWithOrdinal(nameof(System)) && !structType.IsEnum;
}
public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) =>

View file

@ -61,9 +61,7 @@ namespace Server
public static bool IsRunningFromXUnit =>
m_IsRunningFromXUnit ??= AppDomain.CurrentDomain.GetAssemblies()
.Any(
a => a.FullName?
.ToUpperInvariant()
.StartsWith("XUNIT", StringComparison.Ordinal) ?? false
a => a.FullName.InsensitiveStartsWith("XUNIT")
);
public static bool Profiling
@ -346,7 +344,7 @@ namespace Server
foreach (var a in args)
{
if (Insensitive.Equals(a, "-profile"))
if (a.InsensitiveEquals("-profile"))
{
Profiling = true;
}

View file

@ -397,7 +397,7 @@ namespace Server
return null;
}
if (Insensitive.Equals(value, "Internal"))
if (value.InsensitiveEquals("Internal"))
{
return Internal;
}
@ -419,7 +419,7 @@ namespace Server
continue;
}
if (index >= 0 && map.MapIndex == index || Insensitive.Equals(map.Name, value))
if (index >= 0 && map.MapIndex == index || map.Name.InsensitiveEquals(value))
{
return map;
}

View file

@ -28,7 +28,7 @@ namespace Server
while ((line = ip.ReadLine()) != null)
{
if (line.Length == 0 || line.StartsWith("#", StringComparison.Ordinal))
if (line.Length == 0 || line.StartsWithOrdinal("#"))
{
continue;
}

View file

@ -7,6 +7,7 @@ using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Text;
using Server.Accounting;
using Server.Buffers;
using Server.ContextMenus;
using Server.Guilds;
using Server.Gumps;
@ -5674,8 +5675,7 @@ namespace Server
return false;
}
var sb = new StringBuilder(text.Length, text.Length);
using var sb = new ValueStringBuilder(stackalloc char[Math.Min(text.Length, 256)]);
for (var i = 0; i < text.Length; ++i)
{
sb.Append(text[i] != ' ' ? GhostChars.RandomElement() : ' ');

View file

@ -152,7 +152,7 @@ namespace Server
stream.Seek(offset + headerSize, SeekOrigin.Begin);
Span<byte> sourceData = new byte[compressedSize];
Span<byte> sourceData = GC.AllocateUninitializedArray<byte>(compressedSize);
if (stream.Read(sourceData) != compressedSize)
{
@ -163,7 +163,7 @@ namespace Server
if (compressionMethod == 1)
{
data = new byte[decompressedSize];
data = GC.AllocateUninitializedArray<byte>(decompressedSize);
Zlib.Unpack(data, ref decompressedSize, sourceData, compressedSize);
}
else

View file

@ -84,9 +84,9 @@ namespace Server.Network
HuePickers = new List<HuePicker>();
Menus = new List<IMenu>();
Trades = new List<SecureTrade>();
_recvBuffer = new byte[RecvPipeSize];
_recvBuffer = GC.AllocateUninitializedArray<byte>(RecvPipeSize);
RecvPipe = new Pipe<byte>(_recvBuffer);
_sendBuffer = new byte[SendPipeSize];
_sendBuffer = GC.AllocateUninitializedArray<byte>(SendPipeSize);
SendPipe = new Pipe<byte>(_sendBuffer);
m_NextCheckActivity = Core.TickCount + 30000;

View file

@ -13,6 +13,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using Microsoft.Toolkit.HighPerformance.Extensions;
using Server.Diagnostics;
using Server.Gumps;
@ -133,7 +134,8 @@ namespace Server.Network
}
case 0x24: // Use skill
{
if (!int.TryParse(command.Split(' ')[0], out var skillIndex))
var tokenizer = command.Tokenize(' ');
if (!tokenizer.MoveNext() || !int.TryParse(tokenizer.Current, out var skillIndex))
{
break;
}
@ -155,15 +157,11 @@ namespace Server.Network
}
case 0x27: // Cast spell from book
{
var split = command.Split(' ');
var tokenizer = command.Tokenize(' ');
var spellID = (tokenizer.MoveNext() ? Utility.ToInt32(tokenizer.Current) : 0) - 1;
var serial = tokenizer.MoveNext() ? Utility.ToUInt32(tokenizer.Current) : (uint)Serial.MinusOne;
if (split.Length > 0)
{
var spellID = Utility.ToInt32(split[0]) - 1;
var serial = split.Length > 1 ? Utility.ToUInt32(split[1]) : (uint)Serial.MinusOne;
EventSink.InvokeCastSpellRequest(from, spellID, World.FindItem(serial));
}
EventSink.InvokeCastSpellRequest(from, spellID, World.FindItem(serial));
break;
}

View file

@ -74,7 +74,7 @@ namespace Server
for (var i = 0; i < m_RaceNames.Length; ++i)
{
if (Insensitive.Equals(m_RaceNames[i], value))
if (m_RaceNames[i].InsensitiveEquals(value))
{
return m_RaceValues[i];
}

View file

@ -42,7 +42,7 @@ namespace Server
{
m_PrefixStrings = prefixStr;
m_Encoding = Utility.UTF8;
_buffer = new byte[BufferSize];
_buffer = GC.AllocateUninitializedArray<byte>(BufferSize);
}
public virtual long Position => Index;

View file

@ -0,0 +1,102 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: InsensitiveStringHelpers.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
namespace Server
{
public static class InsensitiveStringHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int InsensitiveCompare(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.CompareTo(b, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int InsensitiveCompare(this string a, string b) => StringComparer.Ordinal.Compare(a, b);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveEquals(this ReadOnlySpan<char> a, string b) =>
a.Equals(b, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveEquals(this string a, string b) =>
a?.Equals(b, StringComparison.OrdinalIgnoreCase) ?? b == null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveStartsWith(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.StartsWith(b, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveStartsWith(this string a, string b) =>
a?.StartsWith(b, StringComparison.OrdinalIgnoreCase) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveEndsWith(this string a, string b) =>
a?.EndsWith(b, StringComparison.OrdinalIgnoreCase) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveEndsWith(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.EndsWith(b, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveContains(this ReadOnlySpan<char> a, string b) =>
a.Contains(b, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveContains(this string a, string b) =>
a?.Contains(b, StringComparison.OrdinalIgnoreCase) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveContains(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.Contains(b, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool InsensitiveContains(this string a, char b) =>
a?.Contains(b, StringComparison.OrdinalIgnoreCase) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int InsensitiveIndexOf(this string a, char b) =>
a?.IndexOf(b, StringComparison.OrdinalIgnoreCase) ?? -1;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int InsensitiveIndexOf(this string a, string b) =>
a?.IndexOf(b, StringComparison.OrdinalIgnoreCase) ?? -1;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int InsensitiveIndexOf(this string a, string b, int startIndex) =>
a?.IndexOf(b, startIndex, StringComparison.OrdinalIgnoreCase) ?? -1;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int InsensitiveIndexOf(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.IndexOf(b, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string InsensitiveReplace(this string a, string o, string n) =>
a?.Replace(o, n, StringComparison.OrdinalIgnoreCase);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void InsensitiveRemove(
this ReadOnlySpan<char> a,
ReadOnlySpan<char> b,
Span<char> buffer,
out int size
) => a.Remove(b, StringComparison.OrdinalIgnoreCase, buffer, out size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string InsensitiveRemove(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.Remove(b, StringComparison.OrdinalIgnoreCase);
}
}

View file

@ -0,0 +1,100 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: OrdinalStringHelpers.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Runtime.CompilerServices;
namespace Server
{
public static class OrdinalStringHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CompareOrdinal(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.CompareTo(b, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CompareOrdinal(this string a, string b) => StringComparer.Ordinal.Compare(a, b);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsOrdinal(this ReadOnlySpan<char> a, string b) =>
a.Equals(b, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EqualsOrdinal(this string a, string b) =>
a?.Equals(b, StringComparison.Ordinal) ?? b == null;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWithOrdinal(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.StartsWith(b, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool StartsWithOrdinal(this string a, string b) =>
a?.StartsWith(b, StringComparison.Ordinal) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWithOrdinal(this string a, string b) =>
a?.EndsWith(b, StringComparison.Ordinal) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool EndsWithOrdinal(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.EndsWith(b, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsOrdinal(this ReadOnlySpan<char> a, string b) =>
a.Contains(b, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsOrdinal(this string a, string b) =>
a?.Contains(b, StringComparison.Ordinal) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsOrdinal(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.Contains(b, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static bool ContainsOrdinal(this string a, char b) =>
a?.Contains(b, StringComparison.Ordinal) == true;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfOrdinal(this string a, char b) => a?.IndexOf(b, StringComparison.Ordinal) ?? -1;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfOrdinal(this string a, string b) => a?.IndexOf(b, StringComparison.Ordinal) ?? -1;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfOrdinal(this string a, string b, int startIndex) =>
a?.IndexOf(b, startIndex, StringComparison.Ordinal) ?? -1;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int IndexOfOrdinal(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.IndexOf(b, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string ReplaceOrdinal(this string a, string o, string n) =>
a?.Replace(o, n, StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string RemoveOrdinal(this string a, string b) =>
a?.Replace(b, "", StringComparison.Ordinal);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void RemoveOrdinal(this ReadOnlySpan<char> a, ReadOnlySpan<char> b, Span<char> buffer, out int size) =>
a.Remove(b, StringComparison.Ordinal, buffer, out size);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string RemoveOrdinal(this ReadOnlySpan<char> a, ReadOnlySpan<char> b) =>
a.Remove(b, StringComparison.Ordinal);
}
}

View file

@ -0,0 +1,245 @@
/*************************************************************************
* ModernUO *
* Copyright (C) 2019-2020 - ModernUO Development Team *
* Email: hi@modernuo.com *
* File: StringHelpers.cs *
* *
* This program is free software: you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation, either version 3 of the License, or *
* (at your option) any later version. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
*************************************************************************/
using System;
using System.Buffers;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
namespace Server
{
public static class StringHelpers
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string DefaultIfNullOrEmpty(this string value, string def) => value?.Trim().Length > 0 ? value : def;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void Remove(
this ReadOnlySpan<char> a,
ReadOnlySpan<char> b,
StringComparison comparison,
Span<char> buffer,
out int size
)
{
size = 0;
if (a == null || a.Length == 0)
{
return;
}
var sliced = a;
while (true)
{
var indexOf = sliced.IndexOf(b, comparison);
if (indexOf == -1)
{
indexOf = sliced.Length;
}
if (size + indexOf > buffer.Length)
{
throw new OutOfMemoryException(nameof(buffer));
}
sliced.Slice(0, indexOf).CopyTo(buffer.Slice(size));
size += indexOf;
if (indexOf == sliced.Length)
{
break;
}
sliced = sliced.Slice(indexOf + 1);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Remove(this ReadOnlySpan<char> a, ReadOnlySpan<char> b, StringComparison comparison)
{
if (a == null)
{
return null;
}
if (a.Length == 0)
{
return "";
}
Span<char> span = a.Length < 1024 ? stackalloc char[a.Length] : null;
char[] chrs;
if (span == null)
{
chrs = ArrayPool<char>.Shared.Rent(a.Length);
span = chrs.AsSpan();
}
else
{
chrs = null;
}
a.Remove(b, comparison, span, out var size);
var str = span.Slice(0, size).ToString();
if (chrs != null)
{
ArrayPool<char>.Shared.Return(chrs);
}
return str;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string Capitalize(this string value)
{
if (string.IsNullOrEmpty(value))
{
return value;
}
Span<char> span = value.Length < 1024 ? stackalloc char[value.Length] : null;
char[] chrs;
if (span == null)
{
chrs = ArrayPool<char>.Shared.Rent(value.Length);
span = chrs.AsSpan();
}
else
{
chrs = null;
}
var sliced = value.AsSpan();
// Copy over the previous span
sliced.CopyTo(span);
var index = 0;
while (true)
{
// Special case for titles - words that don't get capitalized
if (sliced.InsensitiveStartsWith("the "))
{
sliced = sliced.Slice(4);
index += 4;
continue;
}
var indexOf = sliced.IndexOf(' ');
span[index] = char.ToUpperInvariant(sliced[0]);
if (indexOf == -1)
{
break;
}
if (indexOf == sliced.Length - 1)
{
break;
}
sliced = sliced.Slice(indexOf + 1);
index += indexOf + 1;
}
var str = span.ToString();
if (chrs != null)
{
ArrayPool<char>.Shared.Return(chrs);
}
return str;
}
public static List<string> Wrap(this string value, int perLine, int maxLines)
{
if ((value = value?.Trim() ?? "").Length <= 0)
{
return null;
}
var span = value.AsSpan();
var list = new List<string>(maxLines);
var lineLength = 0;
while (span.Length > 0)
{
var spaceIndex = span.Slice(lineLength).IndexOf(' ');
if (spaceIndex == -1)
{
spaceIndex = span.Length; // End of the string
}
var newLineLength = lineLength + spaceIndex;
if (newLineLength == perLine || newLineLength == span.Length - 1)
{
// Could be the end of the string
var length = span[newLineLength] == ' ' ? newLineLength - 1 : newLineLength;
list.Add(span.Slice(0, length).ToString());
if (list.Count == maxLines)
{
break;
}
span = span.Slice(newLineLength);
lineLength = 0;
}
else if (newLineLength < perLine)
{
lineLength = newLineLength + 1;
}
else if (lineLength > 0 && lineLength <= perLine)
{
list.Add(span.Slice(0, lineLength - 1).ToString());
if (list.Count == maxLines)
{
break;
}
span = span.Slice(lineLength);
lineLength = spaceIndex;
}
else
{
lineLength = newLineLength;
var index = 0;
while (index < lineLength)
{
lineLength -= perLine;
var length = perLine - (span[index] == ' ' ? 1 : 0);
list.Add(span.Slice(index, length).ToString());
if (list.Count == maxLines)
{
break;
}
index += perLine;
}
span = span.Slice(lineLength);
}
}
return list;
}
}
}

View file

@ -1,5 +1,6 @@
using System;
using System.Buffers.Binary;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
@ -11,6 +12,7 @@ using System.Runtime.InteropServices;
using System.Text;
using System.Xml;
using Microsoft.Toolkit.HighPerformance.Extensions;
using Server.Buffers;
using Server.Random;
namespace Server
@ -144,18 +146,20 @@ namespace Server
ReadOnlySpan<char> chars = s.AsSpan();
StringBuilder stringBuilder = null;
using ValueStringBuilder sb = new ValueStringBuilder(stackalloc char[256]);
var hasDoneAnyReplacements = false;
for (int i = 0, last = 0; i < chars.Length; i++)
{
if (!IsSafeChar(chars[i]) || stringBuilder != null && i == chars.Length - 1)
if (!IsSafeChar(chars[i]) && i == chars.Length - 1)
{
(stringBuilder ??= new StringBuilder()).Append(chars.Slice(last, i - last));
hasDoneAnyReplacements = true;
sb.Append(chars.Slice(last, i - last));
last = i + 1; // Skip the unsafe char
}
}
return stringBuilder?.ToString() ?? s;
return !hasDoneAnyReplacements ? s : sb.ToString();
}
public static void Separate(StringBuilder sb, string value, string separator)
@ -175,9 +179,6 @@ namespace Server
str = Intern(str);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static string DefaultIfNullOrEmpty(this string value, string def) => value?.Trim().Length > 0 ? value : def;
public static IPAddress Intern(IPAddress ipAddress)
{
if (ipAddress == null)
@ -301,7 +302,7 @@ namespace Server
public static bool IPMatch(string val, IPAddress ip, out bool valid)
{
var family = ip.AddressFamily;
var useIPv6 = family == AddressFamily.InterNetworkV6 || val.Contains(':', StringComparison.Ordinal);
var useIPv6 = family == AddressFamily.InterNetworkV6 || val.ContainsOrdinal(':');
ip = useIPv6 ? ip.MapToIPv6() : ip.MapToIPv4();
@ -619,9 +620,9 @@ namespace Server
return "";
}
var hasOpen = str.Contains('<', StringComparison.Ordinal);
var hasClose = str.Contains('>', StringComparison.Ordinal);
var hasPound = str.Contains('#', StringComparison.Ordinal);
var hasOpen = str.ContainsOrdinal('<');
var hasClose = str.ContainsOrdinal('>');
var hasPound = str.ContainsOrdinal('#');
if (!hasOpen && !hasClose && !hasPound)
{
@ -648,9 +649,9 @@ namespace Server
return sb.ToString();
}
public static int InsensitiveCompare(string first, string second) => Insensitive.Compare(first, second);
public static int InsensitiveCompare(string first, string second) => first.InsensitiveCompare(second);
public static bool InsensitiveStartsWith(string first, string second) => Insensitive.StartsWith(first, second);
public static bool InsensitiveStartsWith(string first, string second) => first.InsensitiveStartsWith(second);
public static Direction GetDirection(IPoint2D from, IPoint2D to)
{
@ -990,33 +991,18 @@ namespace Server
}
}
public static List<TOutput> CastListContravariant<TInput, TOutput>(this IReadOnlyCollection<TInput> coll)
where TInput : TOutput
// Using this instead of Linq Cast<> means we can ditch the yield and enforce contravariance
public static HashSet<TOutput> SafeConvertSet<TInput, TOutput>(this IEnumerable<TInput> coll)
where TOutput : TInput => coll.SafeConvert<HashSet<TOutput>, TInput, TOutput>();
public static List<TOutput> SafeConvertList<TInput, TOutput>(this IEnumerable<TInput> coll)
where TOutput : TInput => coll.SafeConvert<List<TOutput>, TInput, TOutput>();
public static TColl SafeConvert<TColl, TInput, TOutput>(this IEnumerable<TInput> coll)
where TOutput : TInput where TColl : ICollection<TOutput>, new()
{
var outputList = new List<TOutput>();
foreach (var entry in coll)
{
outputList.Add(entry);
}
var outputList = new TColl();
return outputList;
}
public static List<TOutput> CastListCovariant<TInput, TOutput>(this IReadOnlyCollection<TInput> coll)
where TOutput : TInput
{
var outputList = new List<TOutput>();
foreach (var entry in coll)
{
outputList.Add((TOutput)entry);
}
return outputList;
}
public static List<TOutput> SafeConvertList<TInput, TOutput>(this IReadOnlyCollection<TInput> coll) where TOutput : class
{
var outputList = new List<TOutput>();
foreach (var entry in coll)
{
if (entry is TOutput outEntry)
@ -1055,14 +1041,14 @@ namespace Server
return t;
}
public static int ToInt32(string value)
public static int ToInt32(ReadOnlySpan<char> value)
{
int i;
#pragma warning disable CA1806 // Do not ignore method results
if (value.StartsWith("0x", StringComparison.Ordinal))
if (value.StartsWithOrdinal("0x"))
{
int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i);
int.TryParse(value.Slice(2), NumberStyles.HexNumber, null, out i);
}
else
{
@ -1073,14 +1059,14 @@ namespace Server
return i;
}
public static uint ToUInt32(string value)
public static uint ToUInt32(ReadOnlySpan<char> value)
{
uint i;
#pragma warning disable CA1806 // Do not ignore method results
if (value.StartsWith("0x", StringComparison.OrdinalIgnoreCase))
if (value.InsensitiveStartsWith("0x"))
{
uint.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i);
uint.TryParse(value.Slice(2), NumberStyles.HexNumber, null, out i);
}
else
{
@ -1091,14 +1077,14 @@ namespace Server
return i;
}
public static bool ToInt32(string value, out int i) =>
value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? int.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i)
public static bool ToInt32(ReadOnlySpan<char> value, out int i) =>
value.InsensitiveStartsWith("0x")
? int.TryParse(value.Slice(2), NumberStyles.HexNumber, null, out i)
: int.TryParse(value, out i);
public static bool ToUInt32(string value, out uint i) =>
value.StartsWith("0x", StringComparison.OrdinalIgnoreCase)
? uint.TryParse(value.Substring(2), NumberStyles.HexNumber, null, out i)
public static bool ToUInt32(ReadOnlySpan<char> value, out uint i) =>
value.InsensitiveStartsWith("0x")
? uint.TryParse(value.Slice(2), NumberStyles.HexNumber, null, out i)
: uint.TryParse(value, out i);
public static int GetXMLInt32(string intString, int defaultValue)

View file

@ -313,7 +313,7 @@ namespace Server
continue;
}
byte[] saveBuffer = new byte[entry.Length];
byte[] saveBuffer = GC.AllocateUninitializedArray<byte>(entry.Length);
reader.Read(saveBuffer, 0, entry.Length);
t.SaveBuffer = new BufferWriter(saveBuffer, true);
}
@ -331,7 +331,7 @@ namespace Server
}
using FileStream bin = new FileStream(dataPath, FileMode.Open, FileAccess.Read, FileShare.Read);
var buffer = new byte[bin.Length];
var buffer = bin.Length <= int.MaxValue ? GC.AllocateUninitializedArray<byte>((int)bin.Length) : new byte[bin.Length];
bin.Read(buffer);
bin.Close();