Search Unity

Linear slowdown in SetParent() as more Text objects are added to Scroll View.

Discussion in 'Scripting' started by ajhmain, Feb 18, 2018.

  1. ajhmain

    ajhmain

    Joined:
    Aug 3, 2017
    Posts:
    4
    I've created a simple logging console using a Scroll View and Vertical Layout components to show debugging and status messages. Each log message is simply a new Text object added to the Content area.

    This works, but after adding about 200 messages the FPS (stats window) begins to steadily decrease. As we reach about 700 messages, FPS has fallen to 15 and just continues getting slower.

    I've read in an older post: recommended ways to avoid performance issues with SetParent() and have tried these but with no effect.

    Here's my current code for adding a message:

    Code (CSharp):
    1.         textComponent.gameObject.transform.SetParent(transform,false);
    2.         textComponent.gameObject.transform.SetSiblingIndex(transform.childCount);
    It seems that on each call to SetParent it is looking at all previously added Text objects for some reason. I must admit I'm not clear on how to add objects to the "end of the hierarchy" as explained in the post. I think the answer to that may avoid the performance issue I'm seeing.

    I realise it's unrealistic to expect to be able to log thousands of messages and that a slowdown is inevitable. However I think a limit of 1000 messages (after which we re-use Text objects) is quite reasonable. It's surprising that just 200 messages produces such a performance drop.

    Thanks in advance for any advice.

    (NB: I'm using "Rect Mask 2D" on the scroll view and see a constant number of Tris in the stats window regardless of message count, so this is not a rendering related issue.)
     
  2. ajhmain

    ajhmain

    Joined:
    Aug 3, 2017
    Posts:
    4
    If I remove or disable the Vertical Layout Group, there is no slowdown at all for up to 1000 Text objects. So the issue seems to be with recalculating layout for all Text objects each time a new one is added.

    As a workaround I found I can add Text objects directly to the Content area with no layout group, but I have to manually calculate the vertical position of each item. This works and displays the messages in the correct positions but now no scrollbars are displayed.

    It looks like the Content area does not expand its size to contain the Text objects. I've applied a Content Size Fitter (set to minSize for both), but it seems to make no difference. I wonder if there is a way to manually resize the Content area based on the number of Text objects it contains?
     
  3. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    I guess I had never put so much information to notice a slowdown before (or realized there was one*).

    If you are calculating the position with their sizes, could you not work out the size that the rect must be and change it?
     
  4. Baste

    Baste

    Joined:
    Jan 24, 2013
    Posts:
    6,334
    So first of all, use the profiler to figure out what's slowing down the game. Guesswork isn't any good.

    Secondly, both the layout groups and the content size fitters has garbage performance. The problem is that if any change happens whatsoever in the canvas they're in, they recalculate all of their data. This means that if you're using the standard way of resizing background to fit text - Content Size Fitter + Layout Group - and you're also scrolling, every scroll is going to recalculate every text background.

    For normal Text components, there's a preferredWidth property, which I think gives you the right width in pixels. For Text Mesh Pro (which you probably should be using!), you can get the bounds of the text mesh, which you can use to calculate the size of the background.

    So what you should set up is that whatever text you're using to set the text, also has responsibility for setting the size of the backdrop. You only call that method whenever the text changes - if the text is static, you only do it once. If you're changing the text a lot, you might want to introduce a script that's in between whatever's setting the text and the text component, and have that do the job.
     
  5. ajhmain

    ajhmain

    Joined:
    Aug 3, 2017
    Posts:
    4
    @methos5k Yes, that's what I plan to try next. I just need to ensure I calculate sizes in such a way that changes to the font/size and UI scaling don't break it.

    @Baste I profiled it last night and it is showing me that it's definitely the layout calculation of the Vertical layout group that is getting slower and slower. Removing it, as described above completely avoids the slowdown but now I need to manually calculate the sizing and positioning of Text objects and the container size.

    It would be great if the Vertical Layout group could be told only to perform layout recalculation for the newly added Text, not all existing children.

    I found this other post on creating a dynamic ScrollView which is what I guess I am looking for. The positioning of Text objects and resizing the container is a little unintuitive though, I guess I'll need to do a bit more reading.
     
  6. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    So, I tested this just to see... The testing was somewhat limited, so it may not be perfect..
    I tried with changing the font size once, and changing the length of the message once. Didn't do much with scaling. If more than 1 message was made per frame, it might be better to group them, too. For some reason, I could not change its position without the coroutine yield.
    Code (csharp):
    1.  
    2. float height = 0;
    3. public Text txt;
    4. public RectTransform content;
    5. void Update()
    6. {
    7.    if (Input.GetKeyDown(KeyCode.M))
    8.    {
    9.       AddMsg("Whatever long message goes here");
    10.    }
    11. }
    12.  
    13. Transform last;
    14. void AddMsg(string msg)
    15. {
    16.    Text t = Instantiate(txt);
    17.     t.text = msg;
    18.     float f = t.preferredHeight;
    19.     t.GetComponent<RectTransform>().sizeDelta = new Vector2(t.GetComponent<RectTransform>().sizeDelta.x, f);
    20.     t.transform.SetParent(content, false);
    21.     StartCoroutine(Move(-height));
    22.     last = t.transform;
    23.     height += f;
    24.     content.sizeDelta = new Vector2(content.sizeDelta.x, height);
    25.   }
    26. IEnumerator Move(float h)
    27. {
    28.    yield return new WaitForEndOfFrame();
    29.    last.localPosition = new Vector3(0, h, 0);
    30. }
     
  7. You really have to watch this Unite presentation by Ian Dundore. (Especially the UI part)
     
  8. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,859
    Absolutely. Just set the sizeDelta of the content RectTransform. You're already manually positioning the Text objects, so you already know how big this thing needs to be.
     
  9. ajhmain

    ajhmain

    Joined:
    Aug 3, 2017
    Posts:
    4
    @methos5k Interesting. I'm still learning UI, so thanks for the example of positioning without layouts. For logging messages I'm using a 'Dispatcher' component based on ThreadPool that let's me log messages from a worker thread safely on the main thread.

    @LurkingNinjaDev Really useful presentation, thanks. So the answer to the slowdown with VerticalLayoutGroup I'm seeing is "don't use VerticalLayoutGroup". I guess I am creating what he calls a "hot UI" in which case it seems manual layout is the the only way to avoid slowdowns. That's ok, it just that it means I need to really need to read up on RectTransform and the how its positioning and sizing properties work. I was hoping the built in layouts could do most of the heavy work and let us override where needed.

    @JoeStrout Yes, I really just need to get more familiar with the RectTransform properties and manual layout in general. I've just gone back to basics with a simple 'Panel', adding prefab Text with various anchor and pivots to try things out visually.
     
    JoeStrout likes this.
  10. As Ian said, they wrote a fool-proof, generic UI, which works (sort of) every time. But it comes with huge assumptions.
    Additionally what you already mentioned above, put the items in the scrollview in an additional canvas, turn off pixel perfect, always add the camera, etc.

    Unity UI is tricky. And of course, far from optimal, but still far better than the immediate UI they provided before. :D

    First rule of using layout groups in unity ui: not using them.
     
    JoeStrout likes this.