diff --git a/Projects/Server/Buffers/PooledArraySpanFormattable.cs b/Projects/Server/Buffers/PooledArraySpanFormattable.cs
new file mode 100644
index 000000000..50e7df384
--- /dev/null
+++ b/Projects/Server/Buffers/PooledArraySpanFormattable.cs
@@ -0,0 +1,71 @@
+/*************************************************************************
+ * ModernUO *
+ * Copyright 2019-2022 - ModernUO Development Team *
+ * Email: hi@modernuo.com *
+ * File: PooledArraySpanFormattable.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 . *
+ *************************************************************************/
+
+#nullable enable
+using System;
+using System.Buffers;
+
+namespace Server.Buffers;
+
+public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
+{
+ private char[] _arrayToReturnToPool;
+ private int _pos;
+
+ public PooledArraySpanFormattable(char[] arrayToReturnToPool, int length)
+ {
+ _arrayToReturnToPool = arrayToReturnToPool;
+ _pos = length;
+ }
+
+ public ReadOnlySpan Chars => _arrayToReturnToPool.AsSpan(.._pos);
+
+ public static implicit operator string(PooledArraySpanFormattable f) => f.ToString();
+
+ public string ToString(string? format = null, IFormatProvider formatProvider = null)
+ {
+ var result = new string(_arrayToReturnToPool.AsSpan(0, _pos));
+ Dispose();
+
+ return result;
+ }
+
+ public bool TryFormat(
+ Span destination, out int charsWritten, ReadOnlySpan format = default,
+ IFormatProvider provider = null
+ )
+ {
+ if (destination.Length < _pos)
+ {
+ charsWritten = 0;
+ return false;
+ }
+
+ _arrayToReturnToPool.AsSpan(0, _pos).CopyTo(destination);
+ Dispose();
+
+ charsWritten = _pos;
+ return true;
+ }
+
+ public void Dispose()
+ {
+ if (_arrayToReturnToPool != null)
+ {
+ ArrayPool.Shared.Return(_arrayToReturnToPool);
+ _arrayToReturnToPool = null;
+ }
+ }
+}
diff --git a/Projects/Server/Buffers/ValueStringBuilder.cs b/Projects/Server/Buffers/ValueStringBuilder.cs
index 316c2960e..99a400036 100644
--- a/Projects/Server/Buffers/ValueStringBuilder.cs
+++ b/Projects/Server/Buffers/ValueStringBuilder.cs
@@ -1,8 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+#nullable enable
using System;
-using System.Globalization;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
@@ -14,6 +14,10 @@ public ref struct ValueStringBuilder
private Span _chars;
private int _length;
+ public ValueStringBuilder() : this(64)
+ {
+ }
+
// If this ctor is used, you cannot pass in stackalloc ROS for append/replace.
public ValueStringBuilder(ReadOnlySpan initialString) : this(initialString.Length)
{
@@ -140,7 +144,7 @@ public ref struct ValueStringBuilder
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Insert(int index, string s)
+ public void Insert(int index, string? s)
{
if (s == null)
{
@@ -160,67 +164,39 @@ public ref struct ValueStringBuilder
_length += count;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Append(char c)
+ public void Append(T value, string? format = null)
{
- int pos = _length;
- if ((uint)pos < (uint)_chars.Length)
+ if (value is IFormattable)
{
- _chars[pos] = c;
- _length = pos + 1;
- }
- else
- {
- GrowAndAppend(c);
- }
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Append(int value, NumberFormatInfo info = null)
- {
- if (value >= 0)
- {
- Append((uint)value);
- return;
- }
-
- Append((info ?? NumberFormatInfo.CurrentInfo).NegativeSign);
- Append((uint)-value);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public unsafe void Append(uint value)
- {
- int bufferLength = value.CountDigits();
-
- int pos = _length;
- if ((uint)pos + (uint)bufferLength >= _chars.Length)
- {
- Grow(bufferLength);
- }
-
- if (bufferLength == 1)
- {
- _chars[pos] = (char)(value + '0');
- _length = pos + 1;
- return;
- }
-
- fixed (char* buffer = _chars[pos..])
- {
- char* p = buffer + bufferLength;
- do
+ if (value is ISpanFormattable)
{
- value = Utility.DivRem(value, 10, out uint remainder);
- *--p = (char)(remainder + '0');
- } while (value != 0);
- }
+ Span destination = _chars[_length..];
+ int charsWritten;
+ while (!((ISpanFormattable)value).TryFormat(destination, out charsWritten, format, default))
+ {
+ Grow(1);
+ }
- _length = pos + bufferLength;
+ if ((uint)charsWritten > (uint)destination.Length)
+ {
+ throw new FormatException("Invalid string");
+ }
+
+ _length += charsWritten;
+ }
+ else
+ {
+ Append(((IFormattable)value).ToString(format, default)); // constrained call avoiding boxing for value types
+ }
+ }
+ else if (value is not null)
+ {
+ Append(value.ToString());
+ }
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void Append(string s)
+ public void Append(string? s)
{
if (s == null)
{
@@ -240,7 +216,7 @@ public ref struct ValueStringBuilder
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public void AppendLine(string s)
+ public void AppendLine(string? s)
{
if (s == null)
{
@@ -261,7 +237,7 @@ public ref struct ValueStringBuilder
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private void AppendSlow(string s)
+ private void AppendSlow(string? s)
{
int pos = _length;
if (pos > _chars.Length - s.Length)
@@ -332,14 +308,6 @@ public ref struct ValueStringBuilder
return _chars.Slice(origPos, length);
}
- [MethodImpl(MethodImplOptions.NoInlining)]
- private void GrowAndAppend(char c)
- {
- Grow(1);
- Append(c);
- }
-
-#nullable enable
///
/// Resize the internal buffer either by doubling current buffer size or
/// by adding to
@@ -469,4 +437,165 @@ public ref struct ValueStringBuilder
_length -= length;
}
+
+ /// Provides a handler used by the language compiler to append interpolated strings into instances.
+ [InterpolatedStringHandler]
+ public ref struct AppendInterpolatedStringHandler
+ {
+ // Implementation note:
+ // As this type is only intended to be targeted by the compiler, public APIs eschew argument validation logic
+ // in a variety of places, e.g. allowing a null input when one isn't expected to produce a NullReferenceException rather
+ // than an ArgumentNullException.
+
+ /// The associated StringBuilder to which to append.
+ internal ValueStringBuilder _stringBuilder;
+
+ /// Creates a handler used to append an interpolated string into a .
+ /// The number of constant characters outside of interpolation expressions in the interpolated string.
+ /// The number of interpolation expressions in the interpolated string.
+ /// The associated StringBuilder to which to append.
+ /// This is intended to be called only by compiler-generated code. Arguments are not validated as they'd otherwise be for members intended to be used directly.
+ public AppendInterpolatedStringHandler(int literalLength, int formattedCount, ValueStringBuilder stringBuilder)
+ {
+ _stringBuilder = stringBuilder;
+ }
+
+ /// Writes the specified string to the handler.
+ /// The string to write.
+ public void AppendLiteral(string value) => _stringBuilder.Append(value);
+
+ // Design note:
+ // This provides the same set of overloads and semantics as DefaultInterpolatedStringHandler.
+
+ /// Writes the specified value to the handler.
+ /// The value to write.
+ public void AppendFormatted(T value) => _stringBuilder.Append(value);
+
+ /// Writes the specified value to the handler.
+ /// The value to write.
+ /// The format string.
+ public void AppendFormatted(T value, string? format) => _stringBuilder.Append(value, format);
+
+ /// Writes the specified value to the handler.
+ /// The value to write.
+ /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
+ public void AppendFormatted(T value, int alignment) =>
+ AppendFormatted(value, alignment, format: null);
+
+ /// Writes the specified value to the handler.
+ /// The value to write.
+ /// The format string.
+ /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
+ public void AppendFormatted(T value, int alignment, string? format)
+ {
+ if (alignment == 0)
+ {
+ // This overload is used as a fallback from several disambiguation overloads, so special-case 0.
+ AppendFormatted(value, format);
+ }
+ else if (alignment < 0)
+ {
+ // Left aligned: format into the handler, then append any additional padding required.
+ int start = _stringBuilder.Length;
+ AppendFormatted(value, format);
+ int paddingRequired = -alignment - (_stringBuilder.Length - start);
+ if (paddingRequired > 0)
+ {
+ _stringBuilder.Append(' ', paddingRequired);
+ }
+ }
+ else
+ {
+ var startingPos = _stringBuilder._length;
+ AppendFormatted(value, format);
+
+ InsertAlignment(startingPos, alignment);
+ }
+ }
+
+ /// Writes the specified character span to the handler.
+ /// The span to write.
+ public void AppendFormatted(ReadOnlySpan value) => _stringBuilder.Append(value);
+
+ /// Writes the specified string of chars to the handler.
+ /// The span to write.
+ /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
+ /// The format string.
+ public void AppendFormatted(ReadOnlySpan value, int alignment = 0, string? format = null)
+ {
+ if (alignment == 0)
+ {
+ _stringBuilder.Append(value);
+ }
+ else
+ {
+ bool leftAlign = false;
+ if (alignment < 0)
+ {
+ leftAlign = true;
+ alignment = -alignment;
+ }
+
+ int paddingRequired = alignment - value.Length;
+ if (paddingRequired <= 0)
+ {
+ _stringBuilder.Append(value);
+ }
+ else if (leftAlign)
+ {
+ _stringBuilder.Append(value);
+ _stringBuilder.Append(' ', paddingRequired);
+ }
+ else
+ {
+ _stringBuilder.Append(' ', paddingRequired);
+ _stringBuilder.Append(value);
+ }
+ }
+ }
+
+ /// Writes the specified value to the handler.
+ /// The value to write.
+ public void AppendFormatted(string? value) => _stringBuilder.Append(value);
+
+ /// Writes the specified value to the handler.
+ /// The value to write.
+ /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
+ /// The format string.
+ public void AppendFormatted(string? value, int alignment = 0, string? format = null) =>
+ // Format is meaningless for strings and doesn't make sense for someone to specify. We have the overload
+ // simply to disambiguate between ROS and object, just in case someone does specify a format, as
+ // string is implicitly convertible to both. Just delegate to the T-based implementation.
+ AppendFormatted(value, alignment, format);
+
+ /// Writes the specified value to the handler.
+ /// The value to write.
+ /// Minimum number of characters that should be written for this value. If the value is negative, it indicates left-aligned and the required minimum is the absolute value.
+ /// The format string.
+ public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
+ // This overload is expected to be used rarely, only if either a) something strongly typed as object is
+ // formatted with both an alignment and a format, or b) the compiler is unable to target type to T. It
+ // exists purely to help make cases from (b) compile. Just delegate to the T-based implementation.
+ AppendFormatted