Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Show numbers (int, float etc.) on TextMesh Pro texts without any GC allocations
//#define TMP_ROUND_DECIMALS // When defined, float and double values are rounded using Math.round
using TMPro;
public static class TMP_IntegerText
{
private static readonly char[] arr = new char[64]; // prefix + number + postfix can't exceed this capacity!
private static int charIndex = 0;
public static void SetText( this TMP_Text text, sbyte number )
{
SetText( text, (int) number );
}
public static void SetText( this TMP_Text text, sbyte number, string prefix, string postfix )
{
SetText( text, (int) number, prefix, postfix );
}
public static void SetText( this TMP_Text text, byte number )
{
SetText( text, (uint) number );
}
public static void SetText( this TMP_Text text, byte number, string prefix, string postfix )
{
SetText( text, (uint) number, prefix, postfix );
}
public static void SetText( this TMP_Text text, short number )
{
SetText( text, (int) number );
}
public static void SetText( this TMP_Text text, short number, string prefix, string postfix )
{
SetText( text, (int) number, prefix, postfix );
}
public static void SetText( this TMP_Text text, ushort number )
{
SetText( text, (uint) number );
}
public static void SetText( this TMP_Text text, ushort number, string prefix, string postfix )
{
SetText( text, (uint) number, prefix, postfix );
}
public static void SetText( this TMP_Text text, int number )
{
SetInteger( number );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, int number, string prefix, string postfix )
{
AddStringToArray( postfix );
SetInteger( number );
AddStringToArray( prefix );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, uint number )
{
SetInteger( number );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, uint number, string prefix, string postfix )
{
AddStringToArray( postfix );
SetInteger( number );
AddStringToArray( prefix );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, long number )
{
SetInteger( number );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, long number, string prefix, string postfix )
{
AddStringToArray( postfix );
SetInteger( number );
AddStringToArray( prefix );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, ulong number )
{
SetInteger( number );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, ulong number, string prefix, string postfix )
{
AddStringToArray( postfix );
SetInteger( number );
AddStringToArray( prefix );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, float number, int precision = 2 )
{
SetFloat( number, precision );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, float number, string prefix, string postfix, int precision = 2 )
{
AddStringToArray( postfix );
SetFloat( number, precision );
AddStringToArray( prefix );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, double number, int precision = 2 )
{
SetFloat( number, precision );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
public static void SetText( this TMP_Text text, double number, string prefix, string postfix, int precision = 2 )
{
AddStringToArray( postfix );
SetFloat( number, precision );
AddStringToArray( prefix );
ReverseArray();
text.SetCharArray( arr, 0, charIndex );
charIndex = 0;
}
private static void SetInteger( int number )
{
if( number == 0 )
arr[charIndex++] = '0';
else
{
bool isNegativeNumber = number < 0;
if( isNegativeNumber )
number = -number;
while( number > 0 )
{
arr[charIndex++] = (char) ( '0' + ( number % 10 ) );
number /= 10;
}
if( isNegativeNumber )
arr[charIndex++] = '-';
}
}
private static void SetInteger( uint number )
{
if( number == 0 )
arr[charIndex++] = '0';
else
{
while( number > 0 )
{
arr[charIndex++] = (char) ( '0' + ( number % 10 ) );
number /= 10;
}
}
}
private static void SetInteger( long number )
{
if( number == 0 )
arr[charIndex++] = '0';
else
{
bool isNegativeNumber = number < 0;
if( isNegativeNumber )
number = -number;
while( number > 0 )
{
arr[charIndex++] = (char) ( '0' + ( number % 10 ) );
number /= 10;
}
if( isNegativeNumber )
arr[charIndex++] = '-';
}
}
private static void SetInteger( ulong number )
{
if( number == 0 )
arr[charIndex++] = '0';
else
{
while( number > 0 )
{
arr[charIndex++] = (char) ( '0' + ( number % 10 ) );
number /= 10;
}
}
}
private static void SetFloat( float number, int precision )
{
bool isNegativeNumber = number < 0f;
if( isNegativeNumber )
number = -number;
uint integer = (uint) number;
float fraction = number - integer;
#if TMP_ROUND_DECIMALS
fraction = (float) System.Math.Round( fraction, precision );
#endif
charIndex += precision;
int truncateAmount = 0;
for( int i = 0; i < precision; i++ )
{
// Floating point arithmetics are not deterministic, try to handle
// edge cases properly via these if conditions
if( fraction >= 0f )
fraction *= 10f;
else
fraction *= -10f;
int digit = (int) fraction;
if( digit <= 0 )
{
digit = 0;
truncateAmount++;
}
else
{
if( digit > 9 )
digit = 9;
truncateAmount = 0;
}
arr[--charIndex] = (char) ( '0' + digit );
fraction -= digit;
}
// Assert: if truncateAmount == precision, then fraction consists of all 0's,
// so don't include the fraction in the text at all
if( truncateAmount < precision )
{
if( truncateAmount > 0 )
{
// Truncate redundant 0's
for( int i = charIndex + truncateAmount, j = i + precision; i < j; i++ )
arr[i - truncateAmount] = arr[i];
}
charIndex += precision - truncateAmount;
arr[charIndex++] = '.';
}
else if( integer == 0 ) // Rare case: -0.0001 with precision 2 results in -0, this line fixes it
isNegativeNumber = false;
SetInteger( integer );
if( isNegativeNumber )
arr[charIndex++] = '-';
}
private static void SetFloat( double number, int precision )
{
bool isNegativeNumber = number < 0f;
if( isNegativeNumber )
number = -number;
ulong integer = (ulong) number;
double fraction = number - integer;
#if TMP_ROUND_DECIMALS
fraction = System.Math.Round( fraction, precision );
#endif
charIndex += precision;
int truncateAmount = 0;
for( int i = 0; i < precision; i++ )
{
// Floating point arithmetics are not deterministic, try to handle
// edge cases properly via these if conditions
if( fraction >= 0.0 )
fraction *= 10.0;
else
fraction *= -10.0;
int digit = (int) fraction;
if( digit <= 0 )
{
digit = 0;
truncateAmount++;
}
else
{
if( digit > 9 )
digit = 9;
truncateAmount = 0;
}
arr[--charIndex] = (char) ( '0' + digit );
fraction -= digit;
}
// Assert: if truncateAmount == precision, then fraction consists of all 0's,
// so don't include the fraction in the text at all
if( truncateAmount < precision )
{
if( truncateAmount > 0 )
{
// Truncate redundant 0's
for( int i = charIndex + truncateAmount, j = i + precision; i < j; i++ )
arr[i - truncateAmount] = arr[i];
}
charIndex += precision - truncateAmount;
arr[charIndex++] = '.';
}
else if( integer == 0 ) // Rare case: -0.0001 with precision 2 results in -0, this line fixes it
isNegativeNumber = false;
SetInteger( integer );
if( isNegativeNumber )
arr[charIndex++] = '-';
}
private static void AddStringToArray( string str )
{
if( str == null || str.Length == 0 )
return;
for( int i = str.Length - 1; i >= 0; i-- )
arr[charIndex++] = str[i];
}
private static void ReverseArray()
{
for( int i = charIndex / 2 - 1; i >= 0; i-- )
{
int j = charIndex - 1 - i;
char temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
}
}
}
@yasirkula

This comment has been minimized.

Copy link
Owner Author

@yasirkula yasirkula commented Sep 4, 2019

How To

  • simply call textMeshProObject.SetText( 12345 );
  • alternatively, call textMeshProObject.SetText( 12345, "Prefix", "postfix" ); to set the text as "Prefix 12345 postfix" (e.g. SetText( 12, null, "$" ) changes the text to "12$")

NOTE: Profiler will show some GC while testing on the Editor but there is no GC in a build.

@yasirkula

This comment has been minimized.

Copy link
Owner Author

@yasirkula yasirkula commented Sep 29, 2019

Added support for float and doubles: textMeshProObject.SetText( 12345.65f );

@thygrrr

This comment has been minimized.

Copy link

@thygrrr thygrrr commented Mar 7, 2021

Nice little extension class! I'm adding zfill / padding to it. Thank you so much for sharing!

Line 372: Can we use string.IsNullOrEmpty() ?
Line 375: Can we use string.CopyTo() ? (after reversal... I kinda see how it's an issue) :)
Line 379: Can we use System.Array.Reverse(Array, Int32, Int32) ?

And if you feel particularly wild:
Line 359: What speaks against AddStringToArrray(NumberFormatInfo.CurrentInfo.NumberDecimalSeparator)
Line 139+159: What speaks against int precision = NumberFormatInfo.CurrentInfo.NumberDecimalDigits (most likely perhaps that you are using a default parameter, which as written now needs to be a compile time constant)

@yasirkula

This comment has been minimized.

Copy link
Owner Author

@yasirkula yasirkula commented Mar 7, 2021

As you said, replicating line 375 with string.CopyTo isn't feasible because first we'd have to reverse the string. Compilers would most probably inline string.IsNullOrEmpty at line 372 so we can use IsNullOrEmpty there. Line 379 is a micro optimization to avoid unnecessary checks in Array.Reverse. But of course, it can be replaced with Array.Reverse if you wish.

After creating this script, I've noticed that TextMeshPro's built-in SetText overloads already support adding int and float values to formatted strings without any GC allocations. Performance-wise, I'm not sure if that function can outperform my solution but it is definitely more flexible.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment