fix: Adds optimized formatter for clilocs (#1045)

Adds an optimized formatter for localization. Example:
```cs
string localizationText = Localization.Format(1050039, "enu", $"{m_Amount}\t{LabelNumber:#}");
```

Fixes #1044
This commit is contained in:
Kamron Batman 2022-06-04 11:53:32 -07:00 committed by GitHub
parent ecbee17690
commit 5f00330a66
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 248 additions and 53 deletions

View file

@ -4,6 +4,7 @@ using Xunit;
namespace Server.Tests.Tests.Buffers; namespace Server.Tests.Tests.Buffers;
[Collection("Sequential Tests")]
public class STArrayPoolTests public class STArrayPoolTests
{ {
[Theory] [Theory]
@ -48,7 +49,7 @@ public class STArrayPoolTests
weakReferences1[i] = new WeakReference(arrays1[i]); weakReferences1[i] = new WeakReference(arrays1[i]);
arrays2[i] = STArrayPool<byte>.Shared.Rent(64); arrays2[i] = STArrayPool<byte>.Shared.Rent(64);
weakReferences2[i] = new WeakReference(arrays1[i]); weakReferences2[i] = new WeakReference(arrays2[i]);
} }
for (var i = 0; i < arrays1.Length; i++) for (var i = 0; i < arrays1.Length; i++)

View file

@ -0,0 +1,19 @@
using Xunit;
namespace Server.Tests;
public class LocalizationEntryTests
{
[Fact]
public void TestClilocAsParameter()
{
Localization.Add("enu", 500002, "This tests ~1_NUMBER~ as parameters.");
Localization.Add("enu", 500003, "clilocs");
string numericFormatter = Localization.Format(500002, "enu", $"{500003:#}");
string stringParam = Localization.Format(500002, "enu", $"{"#500003"}");
Assert.Equal("This tests clilocs as parameters", numericFormatter);
Assert.Equal("This tests clilocs as parameters", stringParam);
}
}

View file

@ -22,11 +22,13 @@ public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
{ {
private char[] _arrayToReturnToPool; private char[] _arrayToReturnToPool;
private int _pos; private int _pos;
private string _value;
public PooledArraySpanFormattable(char[] arrayToReturnToPool, int length) public PooledArraySpanFormattable(char[] arrayToReturnToPool, int length)
{ {
_arrayToReturnToPool = arrayToReturnToPool; _arrayToReturnToPool = arrayToReturnToPool;
_pos = length; _pos = length;
_value = null;
} }
public ReadOnlySpan<char> Chars => _arrayToReturnToPool.AsSpan(.._pos); public ReadOnlySpan<char> Chars => _arrayToReturnToPool.AsSpan(.._pos);
@ -35,10 +37,12 @@ public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
public string ToString(string? format = null, IFormatProvider formatProvider = null) public string ToString(string? format = null, IFormatProvider formatProvider = null)
{ {
var result = new string(_arrayToReturnToPool.AsSpan(0, _pos)); _value ??= new string(_arrayToReturnToPool.AsSpan(0, _pos));
Dispose();
return result; STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
_arrayToReturnToPool = null;
return _value;
} }
public bool TryFormat( public bool TryFormat(
@ -53,18 +57,14 @@ public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
} }
_arrayToReturnToPool.AsSpan(0, _pos).CopyTo(destination); _arrayToReturnToPool.AsSpan(0, _pos).CopyTo(destination);
Dispose();
charsWritten = _pos; charsWritten = _pos;
return true; return true;
} }
public void Dispose() public void Dispose()
{ {
if (_arrayToReturnToPool != null) STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
{ _arrayToReturnToPool = null;
STArrayPool<char>.Shared.Return(_arrayToReturnToPool); this = default; // Defensive clear
_arrayToReturnToPool = null;
}
} }
} }

View file

@ -127,6 +127,11 @@ public class STArrayPool<T> : ArrayPool<T>
buckets[i]?.Trim(ticks, pressure, GetMaxSizeForBucket(i)); buckets[i]?.Trim(ticks, pressure, GetMaxSizeForBucket(i));
} }
if (_cacheBuckets == null)
{
return true;
}
// Under high pressure, release all cached buckets // Under high pressure, release all cached buckets
if (pressure == MemoryPressure.High) if (pressure == MemoryPressure.High)
{ {

View file

@ -42,6 +42,37 @@ public static class Localization
} }
} }
public static void Add(string lang, int number, string text)
{
var entry = new LocalizationEntry(lang, number, text);
if (!_localizations.TryGetValue(lang, out var entries))
{
entries = new Dictionary<int, LocalizationEntry>();
_localizations[lang] = entries;
if (lang == FallbackLanguage)
{
_fallbackEntries ??= entries;
}
}
entries.Add(number, entry);
}
public static bool Remove(string lang, int number)
{
if (!_localizations.TryGetValue(lang, out var entries) || !entries.Remove(number))
{
return false;
}
if (entries.Count == 0)
{
_localizations.Remove(lang);
}
return true;
}
public static Dictionary<int, LocalizationEntry> LoadClilocs(string lang) => public static Dictionary<int, LocalizationEntry> LoadClilocs(string lang) =>
LoadClilocs(lang, Core.FindDataFile($"cliloc.{lang}", false)); LoadClilocs(lang, Core.FindDataFile($"cliloc.{lang}", false));
@ -96,19 +127,6 @@ public static class Localization
public static string GetText(int number, string lang = FallbackLanguage) => public static string GetText(int number, string lang = FallbackLanguage) =>
TryGetLocalization(lang, number, out var entry) ? entry.Text : null; TryGetLocalization(lang, number, out var entry) ? entry.Text : null;
/// <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 = FallbackLanguage, params object[] args) =>
TryGetLocalization(lang, number, out var entry) ? entry.Format(args) : null;
/// <summary> /// <summary>
/// Gets a localization entry using the <see cref="FallbackLanguage" />. /// Gets a localization entry using the <see cref="FallbackLanguage" />.
/// </summary> /// </summary>

View file

@ -84,24 +84,6 @@ public class LocalizationEntry
builder.Dispose(); builder.Dispose();
} }
public string Format(params object[] args)
{
if (args == null || args.Length == 0 || StringFormatter == null)
{
return Text;
}
for (var i = 0; i < args.Length; i++)
{
if (args[i] is string s && s[0] == '#' && int.TryParse(s.AsSpan(1), out var number))
{
args[i] = Localization.GetText(number, Language);
}
}
return string.Format(StringFormatter, args);
}
/// <summary> /// <summary>
/// Creates a formatted string of the localization entry. /// 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. /// Uses string interpolation under the hood. This method is preferably relative to the object array method signature.
@ -256,13 +238,69 @@ public class LocalizationEntry
} }
} }
public void AppendFormatted<T>(T value, string? format) // Each numeric needs its own override
public void AppendFormatted(int value, string? format)
{ {
if (!ReadyToAppend()) if (!ReadyToAppend())
{ {
return; return;
} }
if (!TryAppendCliloc(value, format))
{
AppendFormattedDirect(value, format);
}
}
public void AppendFormatted(uint value, string? format)
{
if (!ReadyToAppend())
{
return;
}
if (!TryAppendCliloc((int)value, format))
{
AppendFormattedDirect(value, format);
}
}
public void AppendFormatted(long value, string? format)
{
if (!ReadyToAppend())
{
return;
}
if (!TryAppendCliloc((int)value, format))
{
AppendFormattedDirect(value, format);
}
}
public void AppendFormatted(ulong value, string? format)
{
if (!ReadyToAppend())
{
return;
}
if (!TryAppendCliloc((int)value, format))
{
AppendFormattedDirect(value, format);
}
}
public void AppendFormatted<T>(T value, string? format)
{
if (ReadyToAppend())
{
AppendFormattedDirect(value, format);
}
}
private void AppendFormattedDirect<T>(T value, string? format)
{
string? s; string? s;
if (value is IFormattable) if (value is IFormattable)
{ {
@ -307,6 +345,95 @@ public class LocalizationEntry
} }
} }
// Each numeric needs its own override
public void AppendFormatted(int value, int alignment, string? format)
{
if (!ReadyToAppend())
{
return;
}
var startingPos = _pos;
if (TryAppendCliloc(value, format))
{
AppendFormatted(value, format);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
else
{
AppendFormattedDirect(value, alignment, format);
}
}
public void AppendFormatted(uint value, int alignment, string? format)
{
if (!ReadyToAppend())
{
return;
}
var startingPos = _pos;
if (TryAppendCliloc((int)value, format))
{
AppendFormatted(value, format);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
else
{
AppendFormattedDirect(value, alignment, format);
}
}
public void AppendFormatted(long value, int alignment, string? format)
{
if (!ReadyToAppend())
{
return;
}
var startingPos = _pos;
if (TryAppendCliloc((int)value, format))
{
AppendFormatted(value, format);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
else
{
AppendFormattedDirect(value, alignment, format);
}
}
public void AppendFormatted(ulong value, int alignment, string? format)
{
if (!ReadyToAppend())
{
return;
}
var startingPos = _pos;
if (TryAppendCliloc((int)value, format))
{
AppendFormatted(value, format);
if (alignment != 0)
{
AppendOrInsertAlignmentIfNeeded(startingPos, alignment);
}
}
else
{
AppendFormattedDirect(value, alignment, format);
}
}
public void AppendFormatted<T>(T value, int alignment, string? format) public void AppendFormatted<T>(T value, int alignment, string? format)
{ {
if (!ReadyToAppend()) if (!ReadyToAppend())
@ -314,6 +441,11 @@ public class LocalizationEntry
return; return;
} }
AppendFormattedDirect(value, alignment, format);
}
private void AppendFormattedDirect<T>(T value, int alignment, string? format)
{
var startingPos = _pos; var startingPos = _pos;
AppendFormatted(value, format); AppendFormatted(value, format);
if (alignment != 0) if (alignment != 0)
@ -324,7 +456,7 @@ public class LocalizationEntry
public void AppendFormatted(ReadOnlySpan<char> value) public void AppendFormatted(ReadOnlySpan<char> value)
{ {
if (!ReadyToAppend() || TryAppendClilocNumber(value)) if (!ReadyToAppend() || TryAppendClilocByNumericString(value))
{ {
return; return;
} }
@ -380,12 +512,33 @@ public class LocalizationEntry
} }
} }
public void AppendFormatted(object? value, int alignment = 0, string? format = null) => public void AppendFormatted(object? value, int alignment = 0, string? format = null)
AppendFormatted<object?>(value, alignment, format); {
if (value is int i)
{
AppendFormatted(i, alignment, format);
}
else if (value is uint ui)
{
AppendFormatted(ui, alignment, format);
}
else if (value is long l)
{
AppendFormatted(l, alignment, format);
}
else if (value is ulong ul)
{
AppendFormatted(ul, alignment, format);
}
else
{
AppendFormatted<object?>(value, alignment, format);
}
}
public void AppendFormatted(string? value) public void AppendFormatted(string? value)
{ {
if (!ReadyToAppend() || TryAppendClilocNumber(value)) if (!ReadyToAppend() || TryAppendClilocByNumericString(value))
{ {
return; return;
} }
@ -403,13 +556,9 @@ public class LocalizationEntry
public void AppendFormatted(string? value, int alignment, string? format = null) => public void AppendFormatted(string? value, int alignment, string? format = null) =>
AppendFormatted<string?>(value, alignment, format); AppendFormatted<string?>(value, alignment, format);
private bool TryAppendClilocNumber(ReadOnlySpan<char> value) public bool TryAppendCliloc(int number, string? format)
{ {
if ( if (format != "#" || !Localization.TryGetLocalization(_lang, number, out var entry))
value[0] != '#' ||
!int.TryParse(value[1..], out var number) ||
!Localization.TryGetLocalization(_lang, number, out var entry)
)
{ {
return false; return false;
} }
@ -428,6 +577,9 @@ public class LocalizationEntry
return true; return true;
} }
private bool TryAppendClilocByNumericString(ReadOnlySpan<char> value) =>
value[0] == '#' && long.TryParse(value[1..], out var number) && TryAppendCliloc((int)number, "#");
private void AppendOrInsertAlignmentIfNeeded(int startingPos, int alignment) private void AppendOrInsertAlignmentIfNeeded(int startingPos, int alignment)
{ {
var charsWritten = _pos - startingPos; var charsWritten = _pos - startingPos;