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:
parent
ecbee17690
commit
5f00330a66
6 changed files with 248 additions and 53 deletions
|
|
@ -4,6 +4,7 @@ using Xunit;
|
|||
|
||||
namespace Server.Tests.Tests.Buffers;
|
||||
|
||||
[Collection("Sequential Tests")]
|
||||
public class STArrayPoolTests
|
||||
{
|
||||
[Theory]
|
||||
|
|
@ -48,7 +49,7 @@ public class STArrayPoolTests
|
|||
weakReferences1[i] = new WeakReference(arrays1[i]);
|
||||
|
||||
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++)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -22,11 +22,13 @@ public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
|
|||
{
|
||||
private char[] _arrayToReturnToPool;
|
||||
private int _pos;
|
||||
private string _value;
|
||||
|
||||
public PooledArraySpanFormattable(char[] arrayToReturnToPool, int length)
|
||||
{
|
||||
_arrayToReturnToPool = arrayToReturnToPool;
|
||||
_pos = length;
|
||||
_value = null;
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
var result = new string(_arrayToReturnToPool.AsSpan(0, _pos));
|
||||
Dispose();
|
||||
_value ??= new string(_arrayToReturnToPool.AsSpan(0, _pos));
|
||||
|
||||
return result;
|
||||
STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
|
||||
_arrayToReturnToPool = null;
|
||||
|
||||
return _value;
|
||||
}
|
||||
|
||||
public bool TryFormat(
|
||||
|
|
@ -53,18 +57,14 @@ public struct PooledArraySpanFormattable : ISpanFormattable, IDisposable
|
|||
}
|
||||
|
||||
_arrayToReturnToPool.AsSpan(0, _pos).CopyTo(destination);
|
||||
Dispose();
|
||||
|
||||
charsWritten = _pos;
|
||||
return true;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_arrayToReturnToPool != null)
|
||||
{
|
||||
STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
|
||||
_arrayToReturnToPool = null;
|
||||
}
|
||||
STArrayPool<char>.Shared.Return(_arrayToReturnToPool);
|
||||
_arrayToReturnToPool = null;
|
||||
this = default; // Defensive clear
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -127,6 +127,11 @@ public class STArrayPool<T> : ArrayPool<T>
|
|||
buckets[i]?.Trim(ticks, pressure, GetMaxSizeForBucket(i));
|
||||
}
|
||||
|
||||
if (_cacheBuckets == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Under high pressure, release all cached buckets
|
||||
if (pressure == MemoryPressure.High)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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) =>
|
||||
LoadClilocs(lang, Core.FindDataFile($"cliloc.{lang}", false));
|
||||
|
||||
|
|
@ -96,19 +127,6 @@ public static class Localization
|
|||
public static string GetText(int number, string lang = FallbackLanguage) =>
|
||||
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>
|
||||
/// Gets a localization entry using the <see cref="FallbackLanguage" />.
|
||||
/// </summary>
|
||||
|
|
|
|||
|
|
@ -84,24 +84,6 @@ public class LocalizationEntry
|
|||
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>
|
||||
/// 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.
|
||||
|
|
@ -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())
|
||||
{
|
||||
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;
|
||||
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)
|
||||
{
|
||||
if (!ReadyToAppend())
|
||||
|
|
@ -314,6 +441,11 @@ public class LocalizationEntry
|
|||
return;
|
||||
}
|
||||
|
||||
AppendFormattedDirect(value, alignment, format);
|
||||
}
|
||||
|
||||
private void AppendFormattedDirect<T>(T value, int alignment, string? format)
|
||||
{
|
||||
var startingPos = _pos;
|
||||
AppendFormatted(value, format);
|
||||
if (alignment != 0)
|
||||
|
|
@ -324,7 +456,7 @@ public class LocalizationEntry
|
|||
|
||||
public void AppendFormatted(ReadOnlySpan<char> value)
|
||||
{
|
||||
if (!ReadyToAppend() || TryAppendClilocNumber(value))
|
||||
if (!ReadyToAppend() || TryAppendClilocByNumericString(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -380,12 +512,33 @@ public class LocalizationEntry
|
|||
}
|
||||
}
|
||||
|
||||
public void AppendFormatted(object? value, int alignment = 0, string? format = null) =>
|
||||
AppendFormatted<object?>(value, alignment, format);
|
||||
public void AppendFormatted(object? value, int alignment = 0, string? format = null)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (!ReadyToAppend() || TryAppendClilocNumber(value))
|
||||
if (!ReadyToAppend() || TryAppendClilocByNumericString(value))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
|
@ -403,13 +556,9 @@ public class LocalizationEntry
|
|||
public void AppendFormatted(string? value, int alignment, string? format = null) =>
|
||||
AppendFormatted<string?>(value, alignment, format);
|
||||
|
||||
private bool TryAppendClilocNumber(ReadOnlySpan<char> value)
|
||||
public bool TryAppendCliloc(int number, string? format)
|
||||
{
|
||||
if (
|
||||
value[0] != '#' ||
|
||||
!int.TryParse(value[1..], out var number) ||
|
||||
!Localization.TryGetLocalization(_lang, number, out var entry)
|
||||
)
|
||||
if (format != "#" || !Localization.TryGetLocalization(_lang, number, out var entry))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
|
@ -428,6 +577,9 @@ public class LocalizationEntry
|
|||
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)
|
||||
{
|
||||
var charsWritten = _pos - startingPos;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue