feat: Adds cliloc support and fixes valuestringbuilder ctor (#1013)
- [X] Adds cliloc support using the following API:
```cs
public static class Localization
{
string GetText(int number);
string GetText(int number, string lang);
string Format(int number, params object[] args);
string Format(int number, string lang, params object[] args);
string Format(int number, $"{arg1}{arg2}{arg3}");
string Format(int number, lang, $"{arg1}{arg2}{arg3}");
bool TryGetLocalization(int number, out LocalizationEntry entry);
bool TryGetLocalization(int number, string lang, out LocalizationEntry entry);
}
public class LocalizationEntry
{
string Language { get; }
string Number { get; }
string Text { get; }
string?[] TextSlices { get; } // Used for string building
string StringFormatter { get; }
string Format(params object[] args);
string Format($"{arg1}{arg2}{arg3}");
}
```
- [X] Fixes a bug with ValueStringBuilder and default initialization size
- [X] Optimizes ValueStringBuilder to use `ISpanFormattable`
- [X] Adds `Append<T>(T value);` support ValueStringBuilder
- [X] Adds `Append($"");` string interpolation support to ValueStringBuider
This commit is contained in:
parent
790cb5d733
commit
be7da3a3dc
8 changed files with 1046 additions and 156 deletions
71
Projects/Server/Buffers/PooledArraySpanFormattable.cs
Normal file
71
Projects/Server/Buffers/PooledArraySpanFormattable.cs
Normal file
|
|
@ -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 <http://www.gnu.org/licenses/>. *
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
#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<char> 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<char> destination, out int charsWritten, ReadOnlySpan<char> 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<char>.Shared.Return(_arrayToReturnToPool);
|
||||||
|
_arrayToReturnToPool = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
// Licensed to the .NET Foundation under one or more agreements.
|
// Licensed to the .NET Foundation under one or more agreements.
|
||||||
// The .NET Foundation licenses this file to you under the MIT license.
|
// The .NET Foundation licenses this file to you under the MIT license.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
|
@ -14,6 +14,10 @@ public ref struct ValueStringBuilder
|
||||||
private Span<char> _chars;
|
private Span<char> _chars;
|
||||||
private int _length;
|
private int _length;
|
||||||
|
|
||||||
|
public ValueStringBuilder() : this(64)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
// If this ctor is used, you cannot pass in stackalloc ROS for append/replace.
|
// If this ctor is used, you cannot pass in stackalloc ROS for append/replace.
|
||||||
public ValueStringBuilder(ReadOnlySpan<char> initialString) : this(initialString.Length)
|
public ValueStringBuilder(ReadOnlySpan<char> initialString) : this(initialString.Length)
|
||||||
{
|
{
|
||||||
|
|
@ -140,7 +144,7 @@ public ref struct ValueStringBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Insert(int index, string s)
|
public void Insert(int index, string? s)
|
||||||
{
|
{
|
||||||
if (s == null)
|
if (s == null)
|
||||||
{
|
{
|
||||||
|
|
@ -160,67 +164,39 @@ public ref struct ValueStringBuilder
|
||||||
_length += count;
|
_length += count;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public void Append<T>(T value, string? format = null)
|
||||||
public void Append(char c)
|
|
||||||
{
|
{
|
||||||
int pos = _length;
|
if (value is IFormattable)
|
||||||
if ((uint)pos < (uint)_chars.Length)
|
|
||||||
{
|
{
|
||||||
_chars[pos] = c;
|
if (value is ISpanFormattable)
|
||||||
_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
|
|
||||||
{
|
{
|
||||||
value = Utility.DivRem(value, 10, out uint remainder);
|
Span<char> destination = _chars[_length..];
|
||||||
*--p = (char)(remainder + '0');
|
int charsWritten;
|
||||||
} while (value != 0);
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void Append(string s)
|
public void Append(string? s)
|
||||||
{
|
{
|
||||||
if (s == null)
|
if (s == null)
|
||||||
{
|
{
|
||||||
|
|
@ -240,7 +216,7 @@ public ref struct ValueStringBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public void AppendLine(string s)
|
public void AppendLine(string? s)
|
||||||
{
|
{
|
||||||
if (s == null)
|
if (s == null)
|
||||||
{
|
{
|
||||||
|
|
@ -261,7 +237,7 @@ public ref struct ValueStringBuilder
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
private void AppendSlow(string s)
|
private void AppendSlow(string? s)
|
||||||
{
|
{
|
||||||
int pos = _length;
|
int pos = _length;
|
||||||
if (pos > _chars.Length - s.Length)
|
if (pos > _chars.Length - s.Length)
|
||||||
|
|
@ -332,14 +308,6 @@ public ref struct ValueStringBuilder
|
||||||
return _chars.Slice(origPos, length);
|
return _chars.Slice(origPos, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
|
||||||
private void GrowAndAppend(char c)
|
|
||||||
{
|
|
||||||
Grow(1);
|
|
||||||
Append(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
#nullable enable
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Resize the internal buffer either by doubling current buffer size or
|
/// Resize the internal buffer either by doubling current buffer size or
|
||||||
/// by adding <paramref name="additionalCapacityBeyondPos"/> to
|
/// by adding <paramref name="additionalCapacityBeyondPos"/> to
|
||||||
|
|
@ -469,4 +437,165 @@ public ref struct ValueStringBuilder
|
||||||
|
|
||||||
_length -= length;
|
_length -= length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>Provides a handler used by the language compiler to append interpolated strings into <see cref="ValueStringBuilder"/> instances.</summary>
|
||||||
|
[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.
|
||||||
|
|
||||||
|
/// <summary>The associated StringBuilder to which to append.</summary>
|
||||||
|
internal ValueStringBuilder _stringBuilder;
|
||||||
|
|
||||||
|
/// <summary>Creates a handler used to append an interpolated string into a <see cref="ValueStringBuilder"/>.</summary>
|
||||||
|
/// <param name="literalLength">The number of constant characters outside of interpolation expressions in the interpolated string.</param>
|
||||||
|
/// <param name="formattedCount">The number of interpolation expressions in the interpolated string.</param>
|
||||||
|
/// <param name="stringBuilder">The associated StringBuilder to which to append.</param>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
public AppendInterpolatedStringHandler(int literalLength, int formattedCount, ValueStringBuilder stringBuilder)
|
||||||
|
{
|
||||||
|
_stringBuilder = stringBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Writes the specified string to the handler.</summary>
|
||||||
|
/// <param name="value">The string to write.</param>
|
||||||
|
public void AppendLiteral(string value) => _stringBuilder.Append(value);
|
||||||
|
|
||||||
|
// Design note:
|
||||||
|
// This provides the same set of overloads and semantics as DefaultInterpolatedStringHandler.
|
||||||
|
|
||||||
|
/// <summary>Writes the specified value to the handler.</summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void AppendFormatted<T>(T value) => _stringBuilder.Append(value);
|
||||||
|
|
||||||
|
/// <summary>Writes the specified value to the handler.</summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
/// <param name="format">The format string.</param>
|
||||||
|
public void AppendFormatted<T>(T value, string? format) => _stringBuilder.Append(value, format);
|
||||||
|
|
||||||
|
/// <summary>Writes the specified value to the handler.</summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
/// <param name="alignment">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.</param>
|
||||||
|
public void AppendFormatted<T>(T value, int alignment) =>
|
||||||
|
AppendFormatted(value, alignment, format: null);
|
||||||
|
|
||||||
|
/// <summary>Writes the specified value to the handler.</summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
/// <param name="format">The format string.</param>
|
||||||
|
/// <param name="alignment">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.</param>
|
||||||
|
public void AppendFormatted<T>(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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Writes the specified character span to the handler.</summary>
|
||||||
|
/// <param name="value">The span to write.</param>
|
||||||
|
public void AppendFormatted(ReadOnlySpan<char> value) => _stringBuilder.Append(value);
|
||||||
|
|
||||||
|
/// <summary>Writes the specified string of chars to the handler.</summary>
|
||||||
|
/// <param name="value">The span to write.</param>
|
||||||
|
/// <param name="alignment">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.</param>
|
||||||
|
/// <param name="format">The format string.</param>
|
||||||
|
public void AppendFormatted(ReadOnlySpan<char> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Writes the specified value to the handler.</summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
public void AppendFormatted(string? value) => _stringBuilder.Append(value);
|
||||||
|
|
||||||
|
/// <summary>Writes the specified value to the handler.</summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
/// <param name="alignment">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.</param>
|
||||||
|
/// <param name="format">The format string.</param>
|
||||||
|
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<char> 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<string?>(value, alignment, format);
|
||||||
|
|
||||||
|
/// <summary>Writes the specified value to the handler.</summary>
|
||||||
|
/// <param name="value">The value to write.</param>
|
||||||
|
/// <param name="alignment">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.</param>
|
||||||
|
/// <param name="format">The format string.</param>
|
||||||
|
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<object?>(value, alignment, format);
|
||||||
|
|
||||||
|
private void InsertAlignment(int startingPos, int alignment)
|
||||||
|
{
|
||||||
|
var charsWritten = _stringBuilder._length - startingPos;
|
||||||
|
|
||||||
|
var paddingNeeded = alignment - charsWritten;
|
||||||
|
if (paddingNeeded > 0)
|
||||||
|
{
|
||||||
|
var chars = _stringBuilder._chars;
|
||||||
|
if (chars.Length - _stringBuilder._length < paddingNeeded)
|
||||||
|
{
|
||||||
|
_stringBuilder.Grow(paddingNeeded);
|
||||||
|
}
|
||||||
|
|
||||||
|
chars.Slice(startingPos, charsWritten).CopyTo(chars[(startingPos + paddingNeeded)..]);
|
||||||
|
chars.Slice(startingPos, paddingNeeded).Fill(' ');
|
||||||
|
|
||||||
|
_stringBuilder._length += paddingNeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
31
Projects/Server/Buffers/ValueStringBuilderExtensions.cs
Normal file
31
Projects/Server/Buffers/ValueStringBuilderExtensions.cs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
/*************************************************************************
|
||||||
|
* ModernUO *
|
||||||
|
* Copyright 2019-2022 - ModernUO Development Team *
|
||||||
|
* Email: hi@modernuo.com *
|
||||||
|
* File: ValueStringBuilderExtensions.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.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Server.Buffers;
|
||||||
|
|
||||||
|
public static class ValueStringBuilderExtensions
|
||||||
|
{
|
||||||
|
// Compiler generated
|
||||||
|
public static void Append(
|
||||||
|
this ref ValueStringBuilder stringBuilder,
|
||||||
|
[InterpolatedStringHandlerArgument("stringBuilder")]
|
||||||
|
ref ValueStringBuilder.AppendInterpolatedStringHandler handler)
|
||||||
|
{
|
||||||
|
// Reassign since the string builder stored on the interpolated handler is by-value
|
||||||
|
stringBuilder = handler._stringBuilder;
|
||||||
|
}
|
||||||
|
}
|
||||||
201
Projects/Server/Localization/Localization.cs
Normal file
201
Projects/Server/Localization/Localization.cs
Normal file
|
|
@ -0,0 +1,201 @@
|
||||||
|
/*************************************************************************
|
||||||
|
* ModernUO *
|
||||||
|
* Copyright 2019-2022 - ModernUO Development Team *
|
||||||
|
* Email: hi@modernuo.com *
|
||||||
|
* File: Localization.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.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text;
|
||||||
|
using Server.Buffers;
|
||||||
|
|
||||||
|
namespace Server;
|
||||||
|
|
||||||
|
public static class Localization
|
||||||
|
{
|
||||||
|
private const bool _loadLocalizationOnStartup = false;
|
||||||
|
public const string FallbackLanguage = "enu";
|
||||||
|
|
||||||
|
private static Dictionary<int, LocalizationEntry> _fallbackEntries;
|
||||||
|
private static Dictionary<string, Dictionary<int, LocalizationEntry>> _localizations = new();
|
||||||
|
|
||||||
|
public static void Configure()
|
||||||
|
{
|
||||||
|
if (_loadLocalizationOnStartup)
|
||||||
|
{
|
||||||
|
foreach (var file in Core.FindDataFileByPattern("cliloc.*"))
|
||||||
|
{
|
||||||
|
var fi = new FileInfo(file);
|
||||||
|
LoadClilocs(fi.Extension.ToLowerInvariant(), file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Dictionary<int, LocalizationEntry> LoadClilocs(string lang) =>
|
||||||
|
LoadClilocs(lang, Core.FindDataFile($"cliloc.{lang}", false));
|
||||||
|
|
||||||
|
private static Dictionary<int, LocalizationEntry> LoadClilocs(string lang, string file)
|
||||||
|
{
|
||||||
|
Dictionary<int, LocalizationEntry> entries = _localizations[lang] = new Dictionary<int, LocalizationEntry>();
|
||||||
|
if (lang == FallbackLanguage)
|
||||||
|
{
|
||||||
|
_fallbackEntries = entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (File.Exists(file))
|
||||||
|
{
|
||||||
|
using var fs = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.Read);
|
||||||
|
using var bin = new BinaryReader(fs);
|
||||||
|
|
||||||
|
bin.ReadInt32();
|
||||||
|
bin.ReadInt16();
|
||||||
|
|
||||||
|
byte[] buffer = null;
|
||||||
|
while (bin.BaseStream.Length != bin.BaseStream.Position)
|
||||||
|
{
|
||||||
|
var number = bin.ReadInt32();
|
||||||
|
var flag = bin.ReadByte(); // Original, Custom, Modified
|
||||||
|
var length = bin.ReadInt16();
|
||||||
|
|
||||||
|
if (buffer == null || buffer.Length < length)
|
||||||
|
{
|
||||||
|
buffer = GC.AllocateUninitializedArray<byte>(length);
|
||||||
|
}
|
||||||
|
|
||||||
|
var bytesRead = bin.Read(buffer, 0, length);
|
||||||
|
if (bytesRead != length)
|
||||||
|
{
|
||||||
|
throw new Exception($"Could not read enough bytes from {file}");
|
||||||
|
}
|
||||||
|
|
||||||
|
var text = Encoding.UTF8.GetString(buffer.AsSpan(0, length));
|
||||||
|
entries[number] = new LocalizationEntry(lang, number, text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the original text for a localization entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="number">Localization number</param>
|
||||||
|
/// <param name="lang">Language in ISO 639‑2 format</param>
|
||||||
|
/// <returns>Original text for the localizaton entry</returns>
|
||||||
|
public static string GetText(int number, string lang = FallbackLanguage) =>
|
||||||
|
TryGetLocalization(lang, number, out var entry) ? entry.Text : null;
|
||||||
|
|
||||||
|
public static string Format(int number, string lang = FallbackLanguage) => GetText(number, lang);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a formatted string of the localization entry using the <see cref="FallbackLanguage" />.
|
||||||
|
/// Uses <see cref="string.Format"/> under the hood.
|
||||||
|
/// Note: This method is not recommended since it uses almost double the memory and 50% more processing.
|
||||||
|
/// Instead use Format with string interpolation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="number">Localization number</param>
|
||||||
|
/// <param name="args">An object array containing zero or more objects to format</param>
|
||||||
|
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided arguments</returns>
|
||||||
|
public static string Format(int number, params object[] args) =>
|
||||||
|
!TryGetLocalization(number, out var entry) ? null : string.Format(entry.StringFormatter, args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a formatted string of the localization entry using the specified language.
|
||||||
|
/// Uses <see cref="string.Format"/> under the hood.
|
||||||
|
/// Note: This method is not recommended since it uses almost double the memory and 50% more processing.
|
||||||
|
/// Instead use Format with string interpolation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="number">Localization number</param>
|
||||||
|
/// <param name="lang">Language in ISO 639-2 format</param>
|
||||||
|
/// <param name="args">An object array containing zero or more objects to format</param>
|
||||||
|
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided arguments</returns>
|
||||||
|
public static string Format(int number, string lang, params object[] args) =>
|
||||||
|
!TryGetLocalization(lang, number, out var entry) ? null : string.Format(entry.StringFormatter, args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a localization entry using the <see cref="FallbackLanguage" />.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="number">Localization number</param>
|
||||||
|
/// <param name="entry">Localization entry retrieved</param>
|
||||||
|
/// <returns>True if the entry exists, otherwise false.</returns>
|
||||||
|
public static bool TryGetLocalization(int number, out LocalizationEntry entry) =>
|
||||||
|
TryGetLocalization(FallbackLanguage, number, out entry);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a localization entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lang">Language in ISO 639-2 format</param>
|
||||||
|
/// <param name="number">Localization number</param>
|
||||||
|
/// <param name="entry">Localization entry retrieved</param>
|
||||||
|
/// <returns>True if the entry exists, otherwise false.</returns>
|
||||||
|
public static bool TryGetLocalization(string lang, int number, out LocalizationEntry entry)
|
||||||
|
{
|
||||||
|
if (lang != FallbackLanguage)
|
||||||
|
{
|
||||||
|
if (!_localizations.TryGetValue(lang, out var entries))
|
||||||
|
{
|
||||||
|
entries = LoadClilocs(lang);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entries.TryGetValue(number, out entry))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_fallbackEntries ??= LoadClilocs(FallbackLanguage);
|
||||||
|
return _fallbackEntries.TryGetValue(number, out entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a formatted string of the localization entry using the specified language.
|
||||||
|
/// Uses string interpolation under the hood. This method is preferably relative to the object array method signature.
|
||||||
|
/// Example:
|
||||||
|
/// Localization.Format(1073841, "jpn", $"{totalItems}{maxItems}{totalWeight}");
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="lang">Language in ISO 639-2 format</param>
|
||||||
|
/// <param name="number">Localization number</param>
|
||||||
|
/// <param name="handler">interpolated string handler used by the compiler as a string builder during compilation</param>
|
||||||
|
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided interpolation arguments</returns>
|
||||||
|
public static PooledArraySpanFormattable Format(
|
||||||
|
int number, string lang,
|
||||||
|
[InterpolatedStringHandlerArgument("number", "lang")]
|
||||||
|
ref LocalizationInterpolationHandler handler
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var chars = handler.ToPooledArray(out var length);
|
||||||
|
handler = default; // Defensive clear
|
||||||
|
return new PooledArraySpanFormattable(chars, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a formatted string of the localization entry using the <see cref="FallbackLanguage" />.
|
||||||
|
/// Uses string interpolation under the hood. This method is preferably relative to the object array method signature.
|
||||||
|
/// Example:
|
||||||
|
/// Localization.Format(1073841, $"{totalItems}{maxItems}{totalWeight}");
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="number">Localization number</param>
|
||||||
|
/// <param name="handler">interpolated string handler used by the compiler as a string builder during compilation</param>
|
||||||
|
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided interpolation arguments</returns>
|
||||||
|
public static PooledArraySpanFormattable Format(
|
||||||
|
int number,
|
||||||
|
[InterpolatedStringHandlerArgument("number")]
|
||||||
|
ref LocalizationInterpolationHandler handler
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var chars = handler.ToPooledArray(out var length);
|
||||||
|
handler = default; // Defensive clear
|
||||||
|
return new PooledArraySpanFormattable(chars, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
104
Projects/Server/Localization/LocalizationEntry.cs
Normal file
104
Projects/Server/Localization/LocalizationEntry.cs
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
/*************************************************************************
|
||||||
|
* ModernUO *
|
||||||
|
* Copyright 2019-2022 - ModernUO Development Team *
|
||||||
|
* Email: hi@modernuo.com *
|
||||||
|
* File: LocalizationEntry.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.Runtime.CompilerServices;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using Server.Buffers;
|
||||||
|
using Server.Collections;
|
||||||
|
|
||||||
|
namespace Server;
|
||||||
|
|
||||||
|
public class LocalizationEntry
|
||||||
|
{
|
||||||
|
private static readonly Regex _textRegex = new(
|
||||||
|
@"~(\d+)[_\w]+~",
|
||||||
|
RegexOptions.Compiled |
|
||||||
|
RegexOptions.IgnoreCase |
|
||||||
|
RegexOptions.Singleline |
|
||||||
|
RegexOptions.CultureInvariant
|
||||||
|
);
|
||||||
|
|
||||||
|
public string Language { get; }
|
||||||
|
public int Number { get; }
|
||||||
|
public string Text { get; }
|
||||||
|
public string[] TextSlices { get; }
|
||||||
|
public string StringFormatter { get; }
|
||||||
|
|
||||||
|
public LocalizationEntry(string lang, int number, string text)
|
||||||
|
{
|
||||||
|
Language = lang;
|
||||||
|
Number = number;
|
||||||
|
Text = text;
|
||||||
|
|
||||||
|
ParseText(text, out var textSlices, out var stringFormatter);
|
||||||
|
TextSlices = textSlices;
|
||||||
|
StringFormatter = stringFormatter;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ParseText(string text, out string[] textSlices, out string stringFormatter)
|
||||||
|
{
|
||||||
|
bool hasMatch = false;
|
||||||
|
var prevIndex = 0;
|
||||||
|
var builder = new ValueStringBuilder(stackalloc char[256]);
|
||||||
|
using var queue = PooledRefQueue<string>.Create();
|
||||||
|
foreach (Match match in _textRegex.Matches(text))
|
||||||
|
{
|
||||||
|
if (prevIndex < match.Index)
|
||||||
|
{
|
||||||
|
var substr = text[prevIndex..match.Index];
|
||||||
|
builder.Append(substr);
|
||||||
|
|
||||||
|
queue.Enqueue(substr);
|
||||||
|
}
|
||||||
|
|
||||||
|
queue.Enqueue(null);
|
||||||
|
hasMatch = true;
|
||||||
|
builder.Append($"{{{int.Parse(match.Groups[1].Value) - 1}}}");
|
||||||
|
prevIndex = match.Index + match.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prevIndex < text.Length - 1)
|
||||||
|
{
|
||||||
|
var substr = prevIndex == 0 ? text : text[prevIndex..];
|
||||||
|
builder.Append(substr);
|
||||||
|
queue.Enqueue(substr);
|
||||||
|
}
|
||||||
|
|
||||||
|
textSlices = queue.ToArray();
|
||||||
|
stringFormatter = hasMatch ? builder.ToString() : null;
|
||||||
|
|
||||||
|
builder.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Format(params object[] args) => string.Format(StringFormatter, args);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a formatted string of the localization entry.
|
||||||
|
/// Uses string interpolation under the hood. This method is preferably relative to the object array method signature.
|
||||||
|
/// Example:
|
||||||
|
/// Format($"{totalItems}{maxItems}{totalWeight}");
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handler">interpolated string handler used by the compiler as a string builder during compilation</param>
|
||||||
|
/// <returns>A copy of the localization text where the placeholder arguments have been replaced with string representations of the provided interpolation arguments</returns>
|
||||||
|
public PooledArraySpanFormattable Format(
|
||||||
|
[InterpolatedStringHandlerArgument("")]
|
||||||
|
ref LocalizationInterpolationHandler handler
|
||||||
|
)
|
||||||
|
{
|
||||||
|
var chars = handler.ToPooledArray(out var length);
|
||||||
|
handler = default; // Defensive clear
|
||||||
|
return new PooledArraySpanFormattable(chars, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
429
Projects/Server/Localization/LocalizationInterpolationHandler.cs
Normal file
429
Projects/Server/Localization/LocalizationInterpolationHandler.cs
Normal file
|
|
@ -0,0 +1,429 @@
|
||||||
|
/*************************************************************************
|
||||||
|
* ModernUO *
|
||||||
|
* Copyright 2019-2022 - ModernUO Development Team *
|
||||||
|
* Email: hi@modernuo.com *
|
||||||
|
* File: LocalizationInterpolationHandler.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/>. *
|
||||||
|
*************************************************************************/
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Buffers;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Server;
|
||||||
|
|
||||||
|
[InterpolatedStringHandler]
|
||||||
|
public ref struct LocalizationInterpolationHandler
|
||||||
|
{
|
||||||
|
private static string[] _empty = Array.Empty<string>();
|
||||||
|
|
||||||
|
private char[]? _arrayToReturnToPool;
|
||||||
|
private Span<char> _chars;
|
||||||
|
private int _pos;
|
||||||
|
|
||||||
|
private int _index;
|
||||||
|
private string?[] _slices;
|
||||||
|
private string? _current;
|
||||||
|
|
||||||
|
public LocalizationInterpolationHandler(int literalLength, int formattedCount, LocalizationEntry entry, out bool isValid)
|
||||||
|
{
|
||||||
|
_slices = entry.TextSlices;
|
||||||
|
_chars = _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(256);
|
||||||
|
isValid = true;
|
||||||
|
|
||||||
|
_pos = 0;
|
||||||
|
_index = 0;
|
||||||
|
_current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalizationInterpolationHandler(int literalLength, int formattedCount, int number, out bool isValid)
|
||||||
|
: this(literalLength, formattedCount, number, Localization.FallbackLanguage, out isValid)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public LocalizationInterpolationHandler(int literalLength, int formattedCount, int number, string lang, out bool isValid)
|
||||||
|
{
|
||||||
|
if (Localization.TryGetLocalization(lang, number, out var entry))
|
||||||
|
{
|
||||||
|
_slices = entry.TextSlices;
|
||||||
|
_chars = _arrayToReturnToPool = ArrayPool<char>.Shared.Rent(256);
|
||||||
|
isValid = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_slices = _empty;
|
||||||
|
_chars = _arrayToReturnToPool = default;
|
||||||
|
isValid = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_pos = 0;
|
||||||
|
_index = 0;
|
||||||
|
_current = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool MoveNext()
|
||||||
|
{
|
||||||
|
if ((uint)_index >= (uint)_slices.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_current = _slices[_index++];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private bool ReadyToAppend()
|
||||||
|
{
|
||||||
|
if (!MoveNext())
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_current == null)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
AppendStringDirect(_current);
|
||||||
|
return MoveNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendLiteral(string value)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AppendStringDirect(string value)
|
||||||
|
{
|
||||||
|
if (value.TryCopyTo(_chars[_pos..]))
|
||||||
|
{
|
||||||
|
_pos += value.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GrowThenCopyString(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted<T>(T value)
|
||||||
|
{
|
||||||
|
if (!ReadyToAppend())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? s;
|
||||||
|
if (value is IFormattable)
|
||||||
|
{
|
||||||
|
// If the value can format itself directly into our buffer, do so.
|
||||||
|
if (value is ISpanFormattable)
|
||||||
|
{
|
||||||
|
int charsWritten;
|
||||||
|
while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, default, default)) // constrained call avoiding boxing for value types
|
||||||
|
{
|
||||||
|
Grow();
|
||||||
|
}
|
||||||
|
|
||||||
|
_pos += charsWritten;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = ((IFormattable)value).ToString(format: null, default); // constrained call avoiding boxing for value types
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s = value?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s is not null)
|
||||||
|
{
|
||||||
|
AppendStringDirect(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted<T>(T value, string? format)
|
||||||
|
{
|
||||||
|
if (!ReadyToAppend())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
string? s;
|
||||||
|
if (value is IFormattable)
|
||||||
|
{
|
||||||
|
// If the value can format itself directly into our buffer, do so.
|
||||||
|
if (value is ISpanFormattable)
|
||||||
|
{
|
||||||
|
int charsWritten;
|
||||||
|
while (!((ISpanFormattable)value).TryFormat(_chars[_pos..], out charsWritten, format, default)) // constrained call avoiding boxing for value types
|
||||||
|
{
|
||||||
|
Grow();
|
||||||
|
}
|
||||||
|
|
||||||
|
_pos += charsWritten;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
s = ((IFormattable)value).ToString(format, default); // constrained call avoiding boxing for value types
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
s = value?.ToString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s is not null)
|
||||||
|
{
|
||||||
|
AppendStringDirect(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted<T>(T value, int alignment)
|
||||||
|
{
|
||||||
|
if (!ReadyToAppend())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startingPos = _pos;
|
||||||
|
AppendFormatted(value);
|
||||||
|
if (alignment != 0)
|
||||||
|
{
|
||||||
|
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted<T>(T value, int alignment, string? format)
|
||||||
|
{
|
||||||
|
if (!ReadyToAppend())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var startingPos = _pos;
|
||||||
|
AppendFormatted(value, format);
|
||||||
|
if (alignment != 0)
|
||||||
|
{
|
||||||
|
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted(ReadOnlySpan<char> value)
|
||||||
|
{
|
||||||
|
if (!ReadyToAppend())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.TryCopyTo(_chars[_pos..]))
|
||||||
|
{
|
||||||
|
_pos += value.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GrowThenCopySpan(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted(ReadOnlySpan<char> value, int alignment, string? format = null)
|
||||||
|
{
|
||||||
|
if (!ReadyToAppend())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftAlign = false;
|
||||||
|
if (alignment < 0)
|
||||||
|
{
|
||||||
|
leftAlign = true;
|
||||||
|
alignment = -alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
var paddingRequired = alignment - value.Length;
|
||||||
|
if (paddingRequired <= 0)
|
||||||
|
{
|
||||||
|
// The value is as large or larger than the required amount of padding,
|
||||||
|
// so just write the value.
|
||||||
|
AppendFormatted(value);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Write the value along with the appropriate padding.
|
||||||
|
EnsureCapacityForAdditionalChars(value.Length + paddingRequired);
|
||||||
|
if (leftAlign)
|
||||||
|
{
|
||||||
|
value.CopyTo(_chars[_pos..]);
|
||||||
|
_pos += value.Length;
|
||||||
|
_chars.Slice(_pos, paddingRequired).Fill(' ');
|
||||||
|
_pos += paddingRequired;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chars.Slice(_pos, paddingRequired).Fill(' ');
|
||||||
|
_pos += paddingRequired;
|
||||||
|
value.CopyTo(_chars[_pos..]);
|
||||||
|
_pos += value.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
|
||||||
|
AppendFormatted<object?>(value, alignment, format);
|
||||||
|
|
||||||
|
public void AppendFormatted(string? value)
|
||||||
|
{
|
||||||
|
if (ReadyToAppend())
|
||||||
|
{
|
||||||
|
if (value?.TryCopyTo(_chars[_pos..]) == true)
|
||||||
|
{
|
||||||
|
_pos += value.Length;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AppendFormattedSlow(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AppendFormatted(string? value, int alignment, string? format = null) =>
|
||||||
|
AppendFormatted<string?>(value, alignment, format);
|
||||||
|
|
||||||
|
private void AppendOrInsertAlignmentIfNeeded(int startingPos, int alignment)
|
||||||
|
{
|
||||||
|
var charsWritten = _pos - startingPos;
|
||||||
|
|
||||||
|
var leftAlign = false;
|
||||||
|
if (alignment < 0)
|
||||||
|
{
|
||||||
|
leftAlign = true;
|
||||||
|
alignment = -alignment;
|
||||||
|
}
|
||||||
|
|
||||||
|
var paddingNeeded = alignment - charsWritten;
|
||||||
|
if (paddingNeeded > 0)
|
||||||
|
{
|
||||||
|
EnsureCapacityForAdditionalChars(paddingNeeded);
|
||||||
|
|
||||||
|
if (leftAlign)
|
||||||
|
{
|
||||||
|
_chars.Slice(_pos, paddingNeeded).Fill(' ');
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_chars.Slice(startingPos, charsWritten).CopyTo(_chars[(startingPos + paddingNeeded)..]);
|
||||||
|
_chars.Slice(startingPos, paddingNeeded).Fill(' ');
|
||||||
|
}
|
||||||
|
|
||||||
|
_pos += paddingNeeded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private void AppendFormattedSlow(string? value)
|
||||||
|
{
|
||||||
|
if (value is not null)
|
||||||
|
{
|
||||||
|
EnsureCapacityForAdditionalChars(value.Length);
|
||||||
|
value.CopyTo(_chars[_pos..]);
|
||||||
|
_pos += value.Length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void EnsureCapacityForAdditionalChars(int additionalChars)
|
||||||
|
{
|
||||||
|
if (_chars.Length - _pos < additionalChars)
|
||||||
|
{
|
||||||
|
Grow(additionalChars);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private void GrowThenCopyString(string value)
|
||||||
|
{
|
||||||
|
Grow(value.Length);
|
||||||
|
value.CopyTo(_chars[_pos..]);
|
||||||
|
_pos += value.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||||
|
private void GrowThenCopySpan(ReadOnlySpan<char> value)
|
||||||
|
{
|
||||||
|
Grow(value.Length);
|
||||||
|
value.CopyTo(_chars[_pos..]);
|
||||||
|
_pos += value.Length;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
|
||||||
|
private void Grow(int additionalChars)
|
||||||
|
{
|
||||||
|
GrowCore((uint)_pos + (uint)additionalChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.NoInlining)] // keep consumers as streamlined as possible
|
||||||
|
private void Grow()
|
||||||
|
{
|
||||||
|
GrowCore((uint)_chars.Length + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private void GrowCore(uint requiredMinCapacity)
|
||||||
|
{
|
||||||
|
var newCapacity = Math.Max(requiredMinCapacity, Math.Min((uint)_chars.Length * 2, 1073741823));
|
||||||
|
var arraySize = (int)Math.Clamp(newCapacity, 256, int.MaxValue);
|
||||||
|
|
||||||
|
var newArray = ArrayPool<char>.Shared.Rent(arraySize);
|
||||||
|
_chars[.._pos].CopyTo(newArray);
|
||||||
|
|
||||||
|
var toReturn = _arrayToReturnToPool;
|
||||||
|
_chars = _arrayToReturnToPool = newArray;
|
||||||
|
|
||||||
|
if (toReturn is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<char>.Shared.Return(toReturn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ReadOnlySpan<char> Text => _chars[.._pos];
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
internal void Clear()
|
||||||
|
{
|
||||||
|
var toReturn = _arrayToReturnToPool;
|
||||||
|
this = default; // defensive clear
|
||||||
|
if (toReturn is not null)
|
||||||
|
{
|
||||||
|
ArrayPool<char>.Shared.Return(toReturn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public string ToStringAndClear()
|
||||||
|
{
|
||||||
|
if (MoveNext() && _current != null)
|
||||||
|
{
|
||||||
|
AppendStringDirect(_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = new string(Text);
|
||||||
|
Clear();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public char[] ToPooledArray(out int length)
|
||||||
|
{
|
||||||
|
if (MoveNext() && _current != null)
|
||||||
|
{
|
||||||
|
AppendStringDirect(_current);
|
||||||
|
}
|
||||||
|
|
||||||
|
length = _pos;
|
||||||
|
return _arrayToReturnToPool;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -274,6 +274,21 @@ namespace Server
|
||||||
return fullPath;
|
return fullPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<string> FindDataFileByPattern(string pattern)
|
||||||
|
{
|
||||||
|
var options = new EnumerationOptions { MatchCasing = MatchCasing.CaseInsensitive };
|
||||||
|
foreach (var p in ServerConfiguration.DataDirectories)
|
||||||
|
{
|
||||||
|
if (Directory.Exists(p))
|
||||||
|
{
|
||||||
|
foreach (var file in Directory.EnumerateFiles(p, pattern, options))
|
||||||
|
{
|
||||||
|
yield return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
private static void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
|
||||||
{
|
{
|
||||||
Console.WriteLine(e.IsTerminating ? "Error:" : "Warning:");
|
Console.WriteLine(e.IsTerminating ? "Error:" : "Warning:");
|
||||||
|
|
|
||||||
|
|
@ -1284,96 +1284,6 @@ namespace Server
|
||||||
return (value + mask) ^ mask;
|
return (value + mask) ^ mask;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static long Abs(this long value)
|
|
||||||
{
|
|
||||||
long mask = value >> 63;
|
|
||||||
return (value + mask) ^ mask;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int CountDigits(this uint value)
|
|
||||||
{
|
|
||||||
int digits = 1;
|
|
||||||
if (value >= 100000)
|
|
||||||
{
|
|
||||||
value /= 100000;
|
|
||||||
digits += 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < 10)
|
|
||||||
{
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
else if (value < 100)
|
|
||||||
{
|
|
||||||
digits++;
|
|
||||||
}
|
|
||||||
else if (value < 1000)
|
|
||||||
{
|
|
||||||
digits += 2;
|
|
||||||
}
|
|
||||||
else if (value < 10000)
|
|
||||||
{
|
|
||||||
digits += 3;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
digits += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
return digits;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static int CountDigits(this int value)
|
|
||||||
{
|
|
||||||
int absValue = Abs(value);
|
|
||||||
|
|
||||||
int digits = 1;
|
|
||||||
if (absValue >= 100000)
|
|
||||||
{
|
|
||||||
absValue /= 100000;
|
|
||||||
digits += 5;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (absValue < 10)
|
|
||||||
{
|
|
||||||
// no-op
|
|
||||||
}
|
|
||||||
else if (absValue < 100)
|
|
||||||
{
|
|
||||||
digits++;
|
|
||||||
}
|
|
||||||
else if (absValue < 1000)
|
|
||||||
{
|
|
||||||
digits += 2;
|
|
||||||
}
|
|
||||||
else if (absValue < 10000)
|
|
||||||
{
|
|
||||||
digits += 3;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
digits += 4;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value < 0)
|
|
||||||
{
|
|
||||||
digits += 1; // negative
|
|
||||||
}
|
|
||||||
|
|
||||||
return digits;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public static uint DivRem(uint a, uint b, out uint result)
|
|
||||||
{
|
|
||||||
uint div = a / b;
|
|
||||||
result = a - div * b;
|
|
||||||
return div;
|
|
||||||
}
|
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static string GetTimeStamp() => Core.Now.ToTimeStamp();
|
public static string GetTimeStamp() => Core.Now.ToTimeStamp();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue