Introduce no GC string formatter to optimize score string formatting.
This commit is contained in:
@@ -4,6 +4,7 @@ using Cryville.Common.Pdt;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Text.Formatting;
|
||||
|
||||
namespace Cryville.Crtr {
|
||||
public class Judge {
|
||||
@@ -217,7 +218,7 @@ namespace Cryville.Crtr {
|
||||
scoreDefs.Add(key, s.Value);
|
||||
scores.Add(key, s.Value.init);
|
||||
scoreStringCache.Add(scoreStringKeys[key], null);
|
||||
scoreStringSrcs.Add(scoreStringKeys[key], new ScoreStringSrc(scoreStringPool, () => GetScoreString(strkey)));
|
||||
scoreStringSrcs.Add(scoreStringKeys[key], new ScoreStringSrc(scoreStringPool, () => scores[key], scoreDefs[key].format));
|
||||
}
|
||||
}
|
||||
void InvalidateScore(int key) {
|
||||
@@ -249,30 +250,32 @@ namespace Cryville.Crtr {
|
||||
return result;
|
||||
}
|
||||
class ScoreStringSrc : PropSrc {
|
||||
readonly Func<string> _cb;
|
||||
readonly Func<float> _cb;
|
||||
readonly string _format;
|
||||
readonly ArrayPool<byte> _pool;
|
||||
byte[] _buf;
|
||||
public ScoreStringSrc(ArrayPool<byte> pool, Func<string> cb)
|
||||
readonly StringBuffer _buf = new StringBuffer();
|
||||
public ScoreStringSrc(ArrayPool<byte> pool, Func<float> cb, string format)
|
||||
: base(PdtInternalType.String) {
|
||||
_pool = pool;
|
||||
_cb = cb;
|
||||
_format = string.Format("{{0:{0}}}", format);
|
||||
}
|
||||
public override void Invalidate() {
|
||||
base.Invalidate();
|
||||
if (_buf != null) {
|
||||
_pool.Return(_buf);
|
||||
_buf = null;
|
||||
if (buf != null) {
|
||||
_pool.Return(buf);
|
||||
base.Invalidate();
|
||||
}
|
||||
}
|
||||
protected override unsafe void InternalGet() {
|
||||
var src = _cb();
|
||||
int strlen = src.Length;
|
||||
_buf.Clear();
|
||||
_buf.AppendFormat(_format, src);
|
||||
int strlen = _buf.Count;
|
||||
buf = _pool.Rent(sizeof(int) + strlen * sizeof(char));
|
||||
fixed (byte* _ptr = buf) {
|
||||
char* ptr = (char*)(_ptr + sizeof(int));
|
||||
*(int*)_ptr = strlen;
|
||||
int i = 0;
|
||||
foreach (var c in src) ptr[i++] = c;
|
||||
char* ptr = (char*)(_ptr + sizeof(int));
|
||||
_buf.CopyTo(ptr, 0, strlen);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,8 @@
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:d8ea0e0da3ad53a45b65c912ffcacab0",
|
||||
"GUID:5686e5ee69d0e084c843d61c240d7fdb"
|
||||
"GUID:5686e5ee69d0e084c843d61c240d7fdb",
|
||||
"GUID:2922aa74af3b2854e81b8a8b286d8206"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
|
8
Assets/Plugins/StringFormatter.meta
Normal file
8
Assets/Plugins/StringFormatter.meta
Normal file
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0c0e7d20046652343bdbe3ed52cb0340
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
333
Assets/Plugins/StringFormatter/CustomNumeric.cs
Normal file
333
Assets/Plugins/StringFormatter/CustomNumeric.cs
Normal file
@@ -0,0 +1,333 @@
|
||||
// Copyright (c) 2023 Cryville
|
||||
//
|
||||
// This file contains an implementation of the stub for custom format string
|
||||
// originally written by Michael Popoloski. Below is the original license.
|
||||
//
|
||||
// Copyright (c) 2015-2017 Michael Popoloski
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
namespace System.Text.Formatting {
|
||||
// this file contains the custom numeric formatting routines split out from the Numeric.cs file
|
||||
unsafe partial class Numeric {
|
||||
static void NumberToCustomFormatString(StringBuffer formatter, ref Number number, StringView specifier, CachedCulture culture) {
|
||||
// Special: Handle special values
|
||||
switch (number.Scale) {
|
||||
case ScaleInf: formatter.Append(number.Sign == 0 ? culture.PositiveInfinity : culture.NegativeInfinity); return;
|
||||
case ScaleNaN: formatter.Append(culture.NaN); return;
|
||||
}
|
||||
// Iteration 1: Split by semicolon
|
||||
int specifierPositiveEnd = IndexOfSectionSeparator(specifier);
|
||||
int specifierNegativeStart = 0, specifierNegativeEnd, specifierZeroStart = 0;
|
||||
if (specifierPositiveEnd == -1) {
|
||||
specifierPositiveEnd = specifierNegativeEnd = specifier.Length;
|
||||
}
|
||||
else {
|
||||
specifierNegativeStart = specifierPositiveEnd + 1;
|
||||
specifierNegativeEnd = IndexOfSectionSeparator(specifier, specifierNegativeStart);
|
||||
if (specifierNegativeEnd == -1) {
|
||||
specifierNegativeEnd = specifier.Length;
|
||||
}
|
||||
else {
|
||||
specifierZeroStart = specifierNegativeEnd + 1;
|
||||
}
|
||||
}
|
||||
// Special: Handle zero
|
||||
if (IsZero(ref number)) {
|
||||
FormatCustomFormatString(formatter, ref number, null, specifier, specifierZeroStart, specifier.Length, culture);
|
||||
return;
|
||||
}
|
||||
// Iteration 2: Divide and round number
|
||||
int originalScale = number.Scale;
|
||||
if (number.Sign == 0) ApplyDivisionAndPrecision(ref number, specifier, 0, specifierPositiveEnd);
|
||||
else ApplyDivisionAndPrecision(ref number, specifier, specifierNegativeStart, specifierNegativeEnd);
|
||||
// Iteration 3: Count; Iteration 4: Format
|
||||
if (IsZero(ref number)) FormatCustomFormatString(formatter, ref number, null, specifier, specifierZeroStart, specifier.Length, culture);
|
||||
else if (number.Sign == 0) FormatCustomFormatString(formatter, ref number, originalScale, specifier, 0, specifierPositiveEnd, culture);
|
||||
else {
|
||||
if (specifierNegativeStart == 0) formatter.Append(culture.NegativeSign);
|
||||
FormatCustomFormatString(formatter, ref number, originalScale, specifier, specifierNegativeStart, specifierNegativeEnd, culture);
|
||||
}
|
||||
}
|
||||
static int IndexOfSectionSeparator(StringView specifier) {
|
||||
return IndexOfSectionSeparator(specifier, 0);
|
||||
}
|
||||
static int IndexOfSectionSeparator(StringView specifier, int index) {
|
||||
if (index < 0 || index > specifier.Length) throw new ArgumentOutOfRangeException("index");
|
||||
char* ptr = specifier.Data;
|
||||
for (; index < specifier.Length; index++) {
|
||||
switch (ptr[index]) {
|
||||
case ';': return index;
|
||||
case '\\':
|
||||
index++;
|
||||
if (index >= specifier.Length) throw new FormatException();
|
||||
break;
|
||||
case '\'':
|
||||
SkipLiteral(specifier, ref index, '\'');
|
||||
break;
|
||||
case '"':
|
||||
SkipLiteral(specifier, ref index, '"');
|
||||
break;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
static bool IsZero(ref Number number) {
|
||||
char* ptr = number.Digits;
|
||||
while (*ptr != '\0') if (*ptr++ != '0') return false;
|
||||
return true;
|
||||
}
|
||||
static void ApplyDivisionAndPrecision(ref Number number, StringView specifier, int index, int end) {
|
||||
int deltaScale = 0, scalingSpecifiers = 0;
|
||||
int integralDigits = 0, decimalDigits = 0;
|
||||
bool decimalFlag = false, exponentialFlag = false;
|
||||
char* ptr = specifier.Data;
|
||||
for (; index < end; index++) {
|
||||
switch (ptr[index]) {
|
||||
case '\\':
|
||||
if (++index >= end) throw new FormatException();
|
||||
break;
|
||||
case '\'':
|
||||
SkipLiteral(specifier, ref index, '\'');
|
||||
break;
|
||||
case '"':
|
||||
SkipLiteral(specifier, ref index, '"');
|
||||
break;
|
||||
case '0':
|
||||
case '#':
|
||||
if (decimalFlag) decimalDigits++;
|
||||
else integralDigits++;
|
||||
scalingSpecifiers = 0;
|
||||
break;
|
||||
case '.': decimalFlag = true; deltaScale -= scalingSpecifiers * 3; break;
|
||||
case ',': scalingSpecifiers++; break;
|
||||
case '%': deltaScale += 2; break;
|
||||
case '‰': deltaScale += 3; break;
|
||||
case 'E':
|
||||
case 'e':
|
||||
if (++index >= end) goto exit;
|
||||
char tc0 = ptr[index];
|
||||
if (tc0 == '+' || tc0 == '-') {
|
||||
if (++index >= end) goto exit;
|
||||
}
|
||||
if (ptr[index] != '0') break;
|
||||
exponentialFlag = true;
|
||||
for (index++; index < end && ptr[index] == '0'; index++) ;
|
||||
index--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
if (exponentialFlag) {
|
||||
number.Scale = integralDigits;
|
||||
RoundNumber(ref number, integralDigits + decimalDigits);
|
||||
}
|
||||
else {
|
||||
number.Scale += deltaScale;
|
||||
RoundNumber(ref number, number.Scale + decimalDigits);
|
||||
}
|
||||
}
|
||||
static void FormatCustomFormatString(StringBuffer formatter, ref Number number, int? originalScale, StringView specifier, int index, int end, CachedCulture culture) {
|
||||
int start = index;
|
||||
int integralDigits = 0, integralZeros = 0,
|
||||
decimalDigits = 0, decimalZeros = 0;
|
||||
bool integralHasZeroFlag = false;
|
||||
int exponentialZeros = 0;
|
||||
bool decimalFlag = false;
|
||||
bool commaFlag = false, groupFlag = false;
|
||||
char* ptr = specifier.Data;
|
||||
for (; index < end; index++) {
|
||||
switch (ptr[index]) {
|
||||
case '\\':
|
||||
if (++index >= end) throw new FormatException();
|
||||
break;
|
||||
case '\'':
|
||||
SkipLiteral(specifier, ref index, '\'');
|
||||
break;
|
||||
case '"':
|
||||
SkipLiteral(specifier, ref index, '"');
|
||||
break;
|
||||
case '0':
|
||||
if (decimalFlag) {
|
||||
if (decimalZeros < decimalDigits)
|
||||
decimalZeros = decimalDigits;
|
||||
decimalDigits++;
|
||||
decimalZeros++;
|
||||
}
|
||||
else {
|
||||
integralDigits++;
|
||||
integralZeros++;
|
||||
integralHasZeroFlag = true;
|
||||
if (commaFlag) groupFlag = true;
|
||||
}
|
||||
break;
|
||||
case '#':
|
||||
if (decimalFlag) {
|
||||
decimalDigits++;
|
||||
}
|
||||
else {
|
||||
integralDigits++;
|
||||
if (integralHasZeroFlag) integralZeros++;
|
||||
if (commaFlag) groupFlag = true;
|
||||
}
|
||||
break;
|
||||
case '.': decimalFlag = true; commaFlag = false; break;
|
||||
case ',': commaFlag = true; break;
|
||||
case 'E':
|
||||
case 'e':
|
||||
if (++index >= end) goto exit;
|
||||
char tc0 = ptr[index];
|
||||
if (tc0 == '+' || tc0 == '-') {
|
||||
if (++index >= end) goto exit;
|
||||
}
|
||||
if (ptr[index] != '0') break;
|
||||
for (; index < end && ptr[index] == '0'; index++) exponentialZeros++;
|
||||
index--;
|
||||
break;
|
||||
}
|
||||
}
|
||||
exit:
|
||||
int currentDigitIndex = 0;
|
||||
int groupIndex = 0, remainingDigitsInGroup = Math.Max(number.Scale, decimalZeros);
|
||||
if (groupFlag) while (true) {
|
||||
int groupSize = culture.NumberData.GroupSizes.ElementAtOrLast(groupIndex);
|
||||
if (remainingDigitsInGroup <= groupSize) break;
|
||||
remainingDigitsInGroup -= groupSize;
|
||||
groupIndex++;
|
||||
}
|
||||
if (number.Scale > integralDigits) while (currentDigitIndex < number.Scale - integralDigits)
|
||||
formatter.AppendIntegralDigit(ref number, ref currentDigitIndex, culture, groupFlag, ref groupIndex, ref remainingDigitsInGroup);
|
||||
decimalFlag = false;
|
||||
for (index = start; index < end; index++) {
|
||||
switch (ptr[index]) {
|
||||
case '\\':
|
||||
if (++index >= end) throw new FormatException();
|
||||
formatter.Append(ptr[index]);
|
||||
break;
|
||||
case '\'':
|
||||
formatter.AppendLiteral(specifier, ref index, '\'');
|
||||
break;
|
||||
case '"':
|
||||
formatter.AppendLiteral(specifier, ref index, '"');
|
||||
break;
|
||||
case '0':
|
||||
case '#':
|
||||
if (decimalFlag) {
|
||||
if (currentDigitIndex < number.Precision) {
|
||||
char digit = number.Digits[currentDigitIndex++];
|
||||
if (digit == '\0') digit = '0';
|
||||
formatter.Append(digit);
|
||||
}
|
||||
else if (decimalZeros > 0)
|
||||
formatter.Append('0');
|
||||
--decimalDigits;
|
||||
--decimalZeros;
|
||||
}
|
||||
else {
|
||||
if (integralDigits <= number.Scale)
|
||||
formatter.AppendIntegralDigit(ref number, ref currentDigitIndex, culture, groupFlag, ref groupIndex, ref remainingDigitsInGroup);
|
||||
else if (integralDigits <= integralZeros)
|
||||
formatter.AppendIntegralDigit('0', culture, groupFlag, ref groupIndex, ref remainingDigitsInGroup);
|
||||
--integralDigits;
|
||||
}
|
||||
break;
|
||||
case '.': formatter.Append(culture.NumberData.DecimalSeparator); decimalFlag = true; break;
|
||||
case ',': break;
|
||||
case '%': formatter.Append(culture.PercentSymbol); break;
|
||||
case '‰': formatter.Append(culture.PerMilleSymbol); break;
|
||||
case 'E':
|
||||
case 'e':
|
||||
char exponentialSymbol = ptr[index];
|
||||
if (++index >= end) {
|
||||
index--;
|
||||
goto default;
|
||||
}
|
||||
char tc0 = ptr[index];
|
||||
bool hasPlusFlag = false;
|
||||
if (tc0 == '+' || tc0 == '-') {
|
||||
if (++index >= end) {
|
||||
index -= 2;
|
||||
goto default;
|
||||
}
|
||||
if (tc0 == '+') hasPlusFlag = true;
|
||||
}
|
||||
if (ptr[index] != '0') {
|
||||
index -= 2;
|
||||
goto default;
|
||||
}
|
||||
for (index++; index < end && ptr[index] == '0'; index++) ;
|
||||
index--;
|
||||
|
||||
int exp = originalScale == null ? 0 : originalScale.Value - number.Scale;
|
||||
formatter.Append(exponentialSymbol);
|
||||
if (exp < 0) {
|
||||
formatter.Append(culture.NegativeSign);
|
||||
exp = -exp;
|
||||
}
|
||||
else if (hasPlusFlag) {
|
||||
formatter.Append(culture.PositiveSign);
|
||||
}
|
||||
Int32ToDecStr(formatter, exp, exponentialZeros, "");
|
||||
break;
|
||||
default:
|
||||
formatter.Append(ptr[index]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void AppendIntegralDigit(this StringBuffer self, ref Number number, ref int index, CachedCulture culture, bool groupFlag, ref int groupIndex, ref int remainingDigitsInGroup) {
|
||||
char digit;
|
||||
if (index >= number.Precision) digit = '0';
|
||||
else digit = number.Digits[index];
|
||||
if (digit == '\0') digit = '0';
|
||||
self.AppendIntegralDigit(digit, culture, groupFlag, ref groupIndex, ref remainingDigitsInGroup);
|
||||
index++;
|
||||
}
|
||||
static void AppendIntegralDigit(this StringBuffer self, char digit, CachedCulture culture, bool groupFlag, ref int groupIndex, ref int remainingDigitsInGroup) {
|
||||
self.Append(digit);
|
||||
if (!groupFlag) return;
|
||||
if (--remainingDigitsInGroup == 0) {
|
||||
if (--groupIndex >= 0) {
|
||||
remainingDigitsInGroup = culture.NumberData.GroupSizes.ElementAtOrLast(groupIndex);
|
||||
self.Append(culture.NumberData.GroupSeparator);
|
||||
}
|
||||
}
|
||||
}
|
||||
static T ElementAtOrLast<T>(this T[] arr, int index) {
|
||||
if (index >= arr.Length) index = arr.Length - 1;
|
||||
return arr[index];
|
||||
}
|
||||
static void SkipLiteral(StringView specifier, ref int index, char delimiter) {
|
||||
while (++index < specifier.Length) {
|
||||
if (specifier.Data[index] == delimiter) return;
|
||||
}
|
||||
throw new FormatException();
|
||||
}
|
||||
static void AppendLiteral(this StringBuffer self, StringView specifier, ref int index, char delimiter) {
|
||||
int start = index + 1;
|
||||
while (++index < specifier.Length) {
|
||||
if (specifier.Data[index] == delimiter) {
|
||||
self.Append(specifier.Data + start, index - start);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
}
|
11
Assets/Plugins/StringFormatter/CustomNumeric.cs.meta
Normal file
11
Assets/Plugins/StringFormatter/CustomNumeric.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 85c9d5fa7edeb9d4d8737903efc95abd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
14
Assets/Plugins/StringFormatter/StringFormatter.asmdef
Normal file
14
Assets/Plugins/StringFormatter/StringFormatter.asmdef
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"name": "StringFormatter",
|
||||
"rootNamespace": "",
|
||||
"references": [],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": true,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [],
|
||||
"noEngineReferences": true
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 2922aa74af3b2854e81b8a8b286d8206
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
2462
Assets/Plugins/StringFormatter/StringFormatter.cs
Normal file
2462
Assets/Plugins/StringFormatter/StringFormatter.cs
Normal file
File diff suppressed because it is too large
Load Diff
11
Assets/Plugins/StringFormatter/StringFormatter.cs.meta
Normal file
11
Assets/Plugins/StringFormatter/StringFormatter.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: be273397b1d98a24cb864cb3e05643cd
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
Reference in New Issue
Block a user