UI timer without Garbage Collection possible?

I have a TMPro UI timer (timeText) updating every frame, using TimeSpan.

    private void UpdateTime()
    {
        timer += Time.deltaTime;
        timeSpan = TimeSpan.FromSeconds(timer);

        timeToDisplay = timeSpan.ToString(@"mm\:ss\:ff");
        timeText.text = timeToDisplay;
    }

This generates 11 calls and 0.9kb of GC / frame.
I’ve tried other methods like calculating minutes, seconds and milliseconds separately, then building the string, but that has even much poorer results as far as calls and GC goes.

Has anyone a more efficient solution that does not generate GC?

It’s impossible to create a new string and not generate garbage, and TextMeshPro may very well be doing something with the string you assign as well to generate even more garbage. Your best bet is to only update it as frequently as needed – I doubt you need the time updated every single frame?

1 Like

You can try the ultra-trashy way: Instantiate 60 texts for 0-59 minutes, instantiate 60 for 0-59 seconds, etc. Then enable/disable the combination you need instead of changing the text. I’m not sure how much GC you’d save from it, but at least the string allocation is gone, and probably the text mesh generation is gone. You’ll still get a canvas rebuild I guess? Not too sure :).

1 Like

You can totally make a new string without allocating.
Well, not a “string” directly, but you can definitely display changing text with numbers and stuff :p.

Just use SetText() on the tmp text. It has specialized handling for floats and supports basic formatting (just decimal digits, that’s all, but it’s enough).
There are multiple overloads for it, take a look.

It 100% does not allocate anything to build the new string.

Another alternative would be using a string builder (however that might allocate some small strings sometimes when dealing with number to text formatting).

4 Likes

Actually came up with something similar, which is perhaps overkill, but works super well.
Made a static array pool of intToStrings from 0 to 99 in awake, and the value of the array is then called via timeSpan.
After generation, no further garbage at runtime, only 1 call.

Here’s the example for seconds (I use the same path for minutes and milliseconds):

    private void UpdateTime()
    {
        timer += Time.deltaTime;
        timeSpan = TimeSpan.FromSeconds(timer);
        timeText.SetText(TimerStringsPool.GetNumberString(timeSpan.Seconds));
    }

I had no idea about the SetText()! That’s on the textmeshpro text instead of UGUI text?

Thanks @dadude123 , I actually missed that API.

Also RE:

Yes creating a string, will always allocate memory and if you want to pass it to any API’s taking string, it will have to be managed memory.
However, there are ways to get around having to allocate a new string every frame just to display the FPS/Time display/Coin counters that you might want to put into the UI. One way is TextMesh Pro’s SetText API

Another is going to take some reflection, unsafe pointers, and other potentially considered hacky ways to reuse one string per UI element that you need to feed with a string.

I’m not gonna delve into the full breadth of that as it is quite dependent on the version of the scripting backend used but here are some resources on that for the adventurous:
http://www.gavpugh.com/2010/04/01/xnac-avoiding-garbage-when-working-with-stringbuilder/
http://www.gavpugh.com/2010/12/21/xnac-stringbuilder-changes-in-xna-4-0/
http://www.nesterovsky-bros.com/weblog/2010/08/25/StringAndStringBuilderInNET4.aspx

1 Like

Text Mesh Pro allows you to give it a char array, so you can just do that. I’ve got GC-free strings like that.

Essentially, to show the time 12:45, you do:

chars[0] = '1';
chars[1] = '2';
chars[2] = ':';
chars[3] = '4';
chars[4] = '5';

textMeshProThing.SetCharArray(chars);

that won’t allocate anything for the string generation. I also seem to remember that you can null-terminate strings using that apporach, so if you set chars[n] to ‘\0’, no chars from n or later will be displayed.

Turning time into chars and putting them into the correct slots is an exercise left to the reader.

4 Likes

Just in case anyone is looking for a copy and paste solution - here’s some seconds to char array code for format 0:00.000

    private void SecondsToCharArray(float timeInSeconds, char[] array) {
        int minutes = (int)(timeInSeconds / 60f);
        array[0] = (char)(48 + (minutes % 10));
        array[1] = ':';

        int seconds = (int)(timeInSeconds - minutes * 60);
        array[2] = (char)(48 + seconds / 10);
        array[3] = (char)(48 + seconds % 10);
        array[4] = '.';

        int milliseconds = (int)((timeInSeconds % 1) * 1000);
        array[5] = (char)(48 + milliseconds / 100);
        array[6] = (char)(48 + (milliseconds % 100) / 10);
        array[7] = (char)(48 + milliseconds % 10);
    }
5 Likes

How about changing the sprite of a Image? There is no string involved in this method.